diff --git a/.clang-format b/.clang-format index 0742e7b8..7af30b94 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,10 @@ BasedOnStyle: LLVM # use LLVM defauls IndentWidth: 4 -ColumnLimit: 80 +ColumnLimit: 100 +UseTab: Never +UseCRLF: false +LineEnding: LF # reset if not set AllowShortFunctionsOnASingleLine: None @@ -35,4 +38,4 @@ BraceWrapping: IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true - SplitEmptyNamespace: true \ No newline at end of file + SplitEmptyNamespace: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..58f46355 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_width = 100 + +[*.{yml, yaml}] +indent_style = space +indent_size = 2 + +[*md] +trim_trailing_whitespace = false diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6bb04571..58b016f0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -38,4 +38,4 @@ Pull request checklist: - Ensure it compiles on **all** supported platforms. - Ensure the PR is focused and changes are relevant to the feature or bug fix. -- Ensure your code is formatted with clang-format using the `clang-format` file in the repo. \ No newline at end of file +- Ensure your code is formatted with clang-format using the `clang-format` file in the repo. diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml index ec4bb386..3ba13e0c 100644 --- a/.github/ISSUE_TEMPLATE/config.yaml +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -1 +1 @@ -blank_issues_enabled: false \ No newline at end of file +blank_issues_enabled: false diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index d921c671..a839fff2 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -17,7 +17,7 @@ jobs: - name: Install clang-format run: | - sudo apt-get update + sudo apt-get update wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 21 diff --git a/.gitignore b/.gitignore index 197514b4..69fcd88c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,11 @@ **.vs # Binaries -**bin -**build +/bin/ +/build/ + +# build reflection +include/pal/pal_config.h # MakeFiles Makefile @@ -15,9 +18,13 @@ Makefile # Visual studio files **.sln +**.slnx **.filters **.user **.vcxproj # docs -docs/**html \ No newline at end of file +docs/**html + +# ppm files +**.ppm diff --git a/CHANGELOG.md b/CHANGELOG.md index a8c99abd..a90051c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,74 +1,134 @@ -# CHANGELOG -## [1.0.0] - 2025-09-27 -- Initial stable release of PAL. - -## [1.0.1] - 2025-10-01 - -**Bugfix release** - improve C/C++ interop and build integration + + + -### Fixed -- Added extern "C" guards to all exported functions so PAL can now be linked from both **C** and **C++** projects. -- Fixed a **condition variable** bug where the wrong thread could acquire the mutex first, causing intermittent locking issues. See **tests/condvar_test.c** -- Updated premake scripts to allow **PAL to be included as a submodule or directly in another workspace** if the build system is **premake**. - -### Notes -- No API or ABI changes -- Safe upgrade from **v1.0** - just rebuild your project after updating. - -## [1.1.0] - 2025-10-17 +## 2.0.0 ### Features -- **Build:** Added Linux platform support across all modules. -- **Core:** Added Linux backend support. -- **Video:** Added X11-based backend support. -- **Thread:** Added Linux backend support. -- **Opengl:** Added Linux backend support. -- **System:** Added Linux backend support. -- **Video:** Added **palCreateCursorFrom()** to create system cursors. -- **Video:** Added **palSetFBConfig()** to select window FBConfig. -- **Video:** Added **PAL_VIDEO_FEATURE_WINDOW_SET_ICON** to `PalVideoFeatures` enum. -- **System:** Added **PAL_PLATFORM_API_COCOA** to `PalPlatformApiType` enum. -- **System:** Added **PAL_PLATFORM_API_ANDRIOD** to `PalPlatformApiType` enum. -- **System:** Added **PAL_PLATFORM_API_UIKIT** to `PalPlatformApiType` enum. -- **System:** Added **PAL_PLATFORM_API_HEADLESS** to `PalPlatformApiType` enum. -- **Core:** Added **PAL_RESULT_INVALID_FBCONFIG_BACKEND** to `PalResult` enum. -### Changed -- **System:** `PalCPUInfo.architecture` is now determined at runtime instead of build time. -- **Opengl:** **palEnumerateGLFBConfigs()** now does not use the `glWindow` paramter. Set to `nullptr` +- Added a graphics system API (`pal2/pal_graphics.h`). (#4) +- Added `palGetResultCode()` to get the result code from a result value. (#4) +- Added `palGetResultSource()` to get the result source from a result value. (#4) +- Added `palGetResultNativeCode()` to get the result native code from a result value. (#4) +- Added `palGetSupportedGLAPIs()` to check supported opengl api types. (#4) +- Added type `PalResultCode` with values: (#4) + - `PAL_RESULT_CODE_INVALID_ARGUMENT` + - `PAL_RESULT_CODE_OUT_OF_MEMORY` + - `PAL_RESULT_CODE_PLATFORM_FAILURE` + - `PAL_RESULT_CODE_TIMEOUT` + - `PAL_RESULT_CODE_INVALID_HANDLE` + - `PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED` + - `PAL_RESULT_CODE_INVALID_OPERATION` + - `PAL_RESULT_CODE_DEVICE_LOST` + - `PAL_RESULT_CODE_OUT_OF_DATE` +- Added type `PalResultSource` with values: (#4) + - `PAL_RESULT_SOURCE_NONE` + - `PAL_RESULT_SOURCE_WIN32` + - `PAL_RESULT_SOURCE_POSIX` + - `PAL_RESULT_SOURCE_EGL` + - `PAL_RESULT_SOURCE_VULKAN` + - `PAL_RESULT_SOURCE_D3D12` + - `PAL_RESULT_SOURCE_METAL` +- Added type `PalGLBackend` with values: (#4) + - `PAL_GL_BACKEND_EGL` + - `PAL_GL_BACKEND_GLX` + - `PAL_GL_BACKEND_WGL` +- Added type `PalGLAPI` with values: (#4) + - `PAL_GL_API_OPENGL` + - `PAL_GL_API_OPENGL_ES` +- Added `_COUNT` constants to all type groups (eg. `PAL_EVENT_TYPE_COUNT`). (#4) +- Added `PAL_GL_GRAPHICS_CARD_NAME_SIZE`,`PAL_GL_VENDOR_NAME_SIZE` and `PAL_GL_VERSION_NAME_SIZE` constants. (#4) -### Fixed -- Fixed a bug where **enter modal mode and exit modal mode** operations triggered only one event. -- Fixed repeated window state event (**minimized**, **maximized**, **restore**). +### Changes -### Notes -- No API or ABI changes - existing Windows code remains compatible. -- Linux video support currently targets **X11** only: **Wayland** is planned for future releases. -- Safe upgrade from **v1.0.1** - just rebuild your project after updating. +- Moved all public header files to `pal2` directory (eg. `pal2/pal_video.h`). (#4) +- `palGetVersion()` now returns `void` and takes a pointer to the struct. (#4) +- `palFormatResult()` now takes two additional parameters (#4) +- Converted all enum types to fixed-width integer types and their values to standalone constants (eg. `PalResult` to `uint64_t`). (#4) +- Removed all previous `PalResult` values except: `PAL_RESULT_SUCCESS` (#4) +- Removed `palGLSetInstance()` function. (#4) +- Removed `palGLGetBackend()` function. (#4) +- Removed `palGetVideoFeaturesEx()` function and `PalVideoFeatures64` enum. (#4) +- Removed `palGetWindowHandleInfoEx()` function and `PalWindowHandleInfoEX` struct. (#4) +- Removed `palGetRawMouseWheelDelta()` function. (#4) +- Removed `palSetPreferredInstance()` function. (#4) +- Removed `palSetFBConfig()` function. (#4) +- Removed `PAL_FBCONFIG_BACKEND_GLES`. (#4) +- `palInitGL()` now takes two additional parameters. (#4) +- `palEnumerateGLFBConfigs()` no longer takes the `glWindow` and `count` now as `uint32_t` (#4) +- `palInitVideo()` now takes an additional parameter. (#4) +- `palGetWindowHandleInfo()` now returns `PalResult` and takes a pointer to the struct. (#4) +- `PalGLInfo` now has `backend` and `api` fields. (#4) +- Renamed `nativeDisplay` to `nativeInstance` in `PalWindowHandleInfo`. (#4) +- Renamed `display` to `instance` in `PalGLWindow`. (#4) +- `PalWindowHandleInfo` now has `nativeHandle1`, `nativeHandle2` and `nativeHandle3` fields. (#4) +- `PalWindowCreateInfo` now has `state`, `appName`, `instanceName`, `fbConfigBackend` and `fbConfigIndex` fields. (#4) +- Removed `maximized` and `minimized` in `PalWindowCreateInfo`. (#4) +- Removed `UintXX` and `IntXX` types in favor of standard `uintXX_t` and `intXX_t`. (#4) +- Removed `_MAX` constants from all type groups (eg. `PAL_EVENT_MAX`). (#4) +- Replaced standard `bool` type and `true`/`false` constants with `PalBool` type and `PAL_TRUE`/`PAL_FALSE`. (#4) +- Renamed `PalGLRelease` to `PalGLReleaseBehavior`. (#4) +- Renamed `palGLGetProcAddress()` to `palGetGLProcAddress()`. (#4) +- Renamed event type constants from `PAL_EVENT_**` to `PAL_EVENT_TYPE_**`. (#4) +- Renamed dispatch mode constants from `PAL_DISPATCH_**` to `PAL_DISPATCH_MODE_**`. (#4) +- Renamed platform type constants from `PAL_PLATFORM_**` to `PAL_PLATFORM_TYPE_**`. (#4) +- Renamed platform api type constants from `PAL_PLATFORM_API_**` to `PAL_PLATFORM_API_TYPE_**`. (#4) +- Renamed cursor type constants from `PAL_CURSOR_**` to `PAL_CURSOR_TYPE_**`. (#4) +- Renamed flash flag constants from `PAL_FLASH_**` to `PAL_FLASH_FLAG_**`. (#4) +- Renamed fbConfig backend type constants from `PAL_FBCONFIG_BACKEND_**` to `PAL_FBCONFIG_BACKEND_**`. (#4) +- Renamed `PalFlashFlag` to `PalFlashFlags`. (#4) +- `palGetMouseDelta()` now takes `dx` and `dy` paramters as `float`. (#4) +- `palEnumerateMonitors()` now takes `count` paramter as `uint32_t`. (#4) +- `palEnumerateMonitorModes()` now takes `count` paramter as `uint32_t`. (#4) +- `palGetClosestGLFBConfig()` now takes `count` paramter as `uint32_t`. (#4) +- `palGetMouseWheelDelta()` now takes `dx` and `dy` paramters as `float`. (#4) +- `palJoinThread()` now takes `retval` paramters as `void**`. (#4) +- `palUnpackUint32()` now takes `data` paramters as `uint64_t`. (#4) +- `palUnpackInt32()` now takes `data` paramters as `uint64_t`. (#4) +- `palUnpackPointer()` now takes `data` paramters as `uint64_t`. (#4) +- `palUnpackFloat()` now takes `data` paramters as `uint64_t`. (#4) +- `palPackUint32()` now returns `uint64_t` instead of `int64_t`. (#4) +- `palPackInt32()` now returns `uint64_t` instead of `int64_t`. (#4) +- `palPackPointer()` now returns `uint64_t` instead of `int64_t`. (#4) +- `palPackFloat()` now returns `uint64_t` instead of `int64_t`. (#4) +- These function now returns `void` instead of `PalResult` and does not do runtime validation anymore: (#4) + - `palGetPlatformInfo()` + - `palGetCPUInfo()` + - `palGetThreadName()` + - `palGetPrimaryMonitor()` + - `palGetMonitorInfo()` + - `palEnumerateMonitorModes()` + - `palGetCurrentMonitorMode()` + - `palMinimizeWindow()` + - `palMaximizeWindow()` + - `palRestoreWindow()` + - `palShowWindow()` + - `palHideWindow()` + - `palGetWindowStyle()` + - `palGetWindowMonitor()` + - `palGetWindowTitle()` + - `palGetWindowPos()` + - `palGetWindowSize()` + - `palGetWindowState()` + - `palGetWindowHandleInfo()` + - `palSetWindowStyle()` + - `palSetWindowTitle()` + - `palSetWindowPos()` + - `palSetWindowSize()` + - `palSetFocusWindow()` + - `palSetWindowIcon()` + - `palClipCursor()` + - `palGetCursorPos()` + - `palSetCursorPos()` + - `palSetWindowCursor()` + - `palSetWindowOpacity()` + - `palFlashWindow()` + - `palSetSwapInterval()` -## [1.2.0] - 2025-10-22 - -### Features -- **Video:** Added **palGetInstance()** to retrieve the native display or instance handle. -- **Video:** Added **palAttachWindow()** for attaching **foreign windows** to PAL. -- **Video:** Added **palDetachWindow()** for detaching **foreign windows** from PAL. -- **Event:** Added **PAL_EVENT_KEYCHAR** to `PalEventType` enum. -- **Event:** Added documentation for event bits(payload) layout. - -### Naming Update -- PAL now stands for **Prime Abstraction Layer**, -reflecting its role as the primary explicit foundation for OS and graphics abstraction. -- All API remains unchanged — this is an identity update only. - -### Tests -- Added multi-threaded OpenGL example: demonstrating **Multi-Threaded OpenGL Rendering**. see **multi_thread_opengl_test.c**. -- Added attaching and detach foreign windows example. see **attach_window_test.c** -- Added key character example. see **char_event_test.c** - -### Notes -- No API or ABI changes - existing code remains compatible. -- Safe upgrade from **v1.1.0** - just rebuild your project after updating. + + + ## [1.3.0] - 2025-11-21 @@ -77,11 +137,11 @@ reflecting its role as the primary explicit foundation for OS and graphics abstr - **Video:** Added **palGetVideoFeaturesEx()** to check old and extended supported features. - **Video:** Added **palGetWindowHandleInfoEx()** to get extended window handles. - **Video:** Added **palGetRawMouseWheelDelta()** to get raw mouse wheel delta. -- **Video:** Added **PAL_CONFIG_BACKEND_GLES** to `PalFBConfigBackend` enum. +- **Video:** Added **PAL_FBCONFIG_BACKEND_GLES** to `PalFBConfigBackend` enum. - **Video:** Added **palSetPreferredInstance()** to set the native instance or display PAL video should use rather than creating a new one. -- **Core:** Added **palPackFloat()** to combine two floats into a single Int64 integer. -- **Core:** Added **palUnpackFloat()** to retreive two floats from a single Int64 integer. +- **Core:** Added **palPackFloat()** to combine two floats into a single int64_t integer. +- **Core:** Added **palUnpackFloat()** to retreive two floats from a single int64_t integer. - **OpenGL:** Added **palGLSetInstance()** to set the native instance or display PAL opengl should use. This must be set before calling **palInitGL()**. - **OpenGL:** Added **palGLGetBackend()** to get the opengl backend. @@ -121,3 +181,88 @@ void* retval; palJoinThread(thread, &retval); ``` + + + + +## [1.2.0] - 2025-10-22 + +### Features +- **Video:** Added **palGetInstance()** to retrieve the native display or instance handle. +- **Video:** Added **palAttachWindow()** for attaching **foreign windows** to PAL. +- **Video:** Added **palDetachWindow()** for detaching **foreign windows** from PAL. +- **Event:** Added **PAL_EVENT_KEYCHAR** to `PalEventType` enum. +- **Event:** Added documentation for event bits(payload) layout. + +### Naming Update +- PAL now stands for **Prime Abstraction Layer**, +reflecting its role as the primary explicit foundation for OS and graphics abstraction. +- All API remains unchanged — this is an identity update only. + +### Tests +- Added multi-threaded OpenGL example: demonstrating **Multi-Threaded OpenGL Rendering**. see **multi_thread_opengl_test.c**. +- Added attaching and detach foreign windows example. see **attach_window_test.c** +- Added key character example. see **char_event_test.c** + +### Notes +- No API or ABI changes - existing code remains compatible. +- Safe upgrade from **v1.1.0** - just rebuild your project after updating. + + + + + +## [1.1.0] - 2025-10-17 + +### Features +- **Build:** Added Linux platform support across all modules. +- **Core:** Added Linux backend support. +- **Video:** Added X11-based backend support. +- **Thread:** Added Linux backend support. +- **Opengl:** Added Linux backend support. +- **System:** Added Linux backend support. +- **Video:** Added **palCreateCursorFrom()** to create system cursors. +- **Video:** Added **palSetFBConfig()** to select window FBConfig. +- **Video:** Added **PAL_VIDEO_FEATURE_WINDOW_SET_ICON** to `PalVideoFeatures` enum. +- **System:** Added **PAL_PLATFORM_API_COCOA** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_ANDRIOD** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_UIKIT** to `PalPlatformApiType` enum. +- **System:** Added **PAL_PLATFORM_API_HEADLESS** to `PalPlatformApiType` enum. +- **Core:** Added **PAL_RESULT_INVALID_FBCONFIG_BACKEND** to `PalResult` enum. + +### Changed +- **System:** `PalCPUInfo.architecture` is now determined at runtime instead of build time. +- **Opengl:** **palEnumerateGLFBConfigs()** now does not use the `glWindow` paramter. Set to `nullptr` + +### Fixed +- Fixed a bug where **enter modal mode and exit modal mode** operations triggered only one event. +- Fixed repeated window state event (**minimized**, **maximized**, **restore**). + +### Notes +- No API or ABI changes - existing Windows code remains compatible. +- Linux video support currently targets **X11** only: **Wayland** is planned for future releases. +- Safe upgrade from **v1.0.1** - just rebuild your project after updating. + + + + + +## [1.0.1] - 2025-10-01 + +**Bugfix release** - improve C/C++ interop and build integration + +### Fixed +- Added extern "C" guards to all exported functions so PAL can now be linked from both **C** and **C++** projects. +- Fixed a **condition variable** bug where the wrong thread could acquire the mutex first, causing intermittent locking issues. See **tests/condvar_test.c** +- Updated premake scripts to allow **PAL to be included as a submodule or directly in another workspace** if the build system is **premake**. + +### Notes +- No API or ABI changes +- Safe upgrade from **v1.0** - just rebuild your project after updating. + + + + + +## [1.0.0] - 2025-09-27 +- Initial stable release of PAL. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index ba743483..e86100c0 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,8 @@ zlib License -Copyright (C) 2025 Nicholas Agbo - +Copyright (C) 2025-2026 Nicholas Agbo + This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. @@ -10,11 +10,11 @@ arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be - appreciated but is not required. + appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md index 6438f822..74af4d32 100644 --- a/README.md +++ b/README.md @@ -4,159 +4,84 @@ ![Language: C99](https://img.shields.io/badge/language-C99-green.svg) ## Overview +PAL is a lightweight, low-level, explicit cross-platform abstraction layer in C over +platform and graphics APIs with support for modular builds and custom backends. +PAL is stateless and transparent. Queries return current state, +reflecting changes made through native API calls. -PAL is a lightweight, low-level, cross-platform abstraction layer in **C**, designed to be **explicit** and as close to the **OS** as possible — similar in philosophy to Vulkan. It gives you precise control without hidden behavior, making it ideal for developers who want performance and predictability. +PAL supports Windows, Linux, Vulkan and D3D12. Both Wayland and X11 are supported on Linux. -PAL is transparent. All queries — window size, position, monitor info, and more — reflect the current platform state. Using PAL is like working directly with the OS: it applies no hidden logic, makes no assumptions, and leaves behavior fully in your control. - -The goal is very simple, write low-level cross-platform code without having per platform files -all over the place. Example: `renderer_vulkan`, `renderer_d3d12`, `window_win32`, etc. -PAL makes it possible to safely mix native API with its API in a very straight forward way. This is one of the main reasons why PAL exists. - -This approach gives you total control: you handle events, manage resources, and cache state explicitly. PAL provides the building blocks; how you use them — whether for simple applications or advanced frameworks — is entirely up to you. - -Example – Get Window Size -```c -// Direct query from the platform — not cached by PAL -palGetWindowSize(window, &w, &h); -``` -> Note: palGetWindowSize queries the OS directly. If your application needs continuous updates (e.g., window moves or resizes frequently), it is more efficient to listen to PAL events rather than repeatedly querying the OS. This ensures your app stays performant. - ---- - -## Why PAL? - -While libraries like SDL or GLFW focus on simplifying development -through high-level abstractions. **PAL is different:** - -- ✅ **Explicit**: You decide how memory, events, and handles are managed. -- ✅ **Low Overhead**: PAL is close to raw OS calls, ensuring performance. -- ✅ **Modular**: Pick only the subsystems you need (video, event, threading, OpenGL, etc.). -- ✅ **Extendable**: Plug in your own backends (event queue, allocator, etc.). -- ✅ **Transparent**: Exposes raw OS handles when you need them. - ---- - -## Quick Start - -Here’s the smallest program that opens a PAL window: - -```c -#include "pal/pal_video.h" +PAL is released under the [Zlib License](https://opensource.org/licenses/Zlib). -int main() { - PalEventDriver* driver = nullptr; - PalEventDriverCreateInfo info = {0}; - palCreateEventDriver(&info, &driver); +## Building PAL +PAL is written in C99 and uses Premake as its build system. PAL supports Windows Vista and later. PAL can be built with GCC, Clang and MSVC. Build options are configure with [pal_config.lua](./pal_config.lua). `true` to enable or `false` to disable a build option. It is recommended to not disable `PAL_BUILD_ABI_DUMP` build option. - palInitVideo(nullptr, driver); +X11 needs XRandR (1.2+) and libXcursor. - PalWindow* window = nullptr; - PalWindowCreateInfo w = {0}; - w.width = 640; - w.height = 480; - w.title = "Hello PAL"; - w.show = true; - palCreateWindow(&w, &window); +See below on how to generate project files for each compiler and toolset. PAL generates **.vscode** folder when generating GNU Make projects. - while (1) { - palUpdateVideo(); - PalEvent e; - while (palPollEvent(driver, &e)) { - if (e.type == PAL_EVENT_WINDOW_CLOSE) return 0; - } - } -} +### Windows +GNU Make (GCC): +```bash +premake\premake5.exe gmake ``` -➡️ Build and run this, and you’ll get a cross-platform window managed entirely by PAL. - -For more detailed examples, see the [tests folder](./tests) tests folder, which contains full usage scenarios and validation cases. - ---- - -## Philosophy -- PAL is a thin layer over the OS, not a framework or library. -- Queries return the current platform state, reflecting any changes made through direct OS calls. -- Developers are responsible for state tracking, caching, and event handling. -- PAL enables cross-platform consistency while preserving full OS behavior and control. -- Advanced users can build libraries or frameworks on top of PAL. -- Minimal overhead (close to raw OS calls) -- Explicit API (no hidden behavior or defaults) -- Event system supporting both polling and callbacks -- Written in C for easy integration -- Stateless: Opaque handles, no internal caching -- No lowest common denominator: exposes platform capabilities directly -- Modular builds: include only the subsystems you need - ---- - -## Supported Platforms -- Windows (Vista+) -- Linux (X11) -- Linux (Wayland) - -## Planned Platforms -- macOS (Cocoa) -- Android -- iOS - -## Dependencies -- Standard C library -- Platform SDKs (Win32, X11, Cocoa, etc.) -- [Make for Windows](https://www.gnu.org/software/make/) (if not using Visual Studio) -- XRandR (1.2+) for X11 -- libXcursor for X11 - -## Compilers -- GCC -- Clang -- MSVC - ---- - -## Build - -PAL is written in **C99** and uses **Premake** as its build system. Configure modules via [pal_config.lua](./pal_config.lua). -See [pal_config.h](./include/pal/pal_config.h) to see the reflection of modules that will be built. +GNU Make (Clang): +```bash +premake\premake5.exe gmake --compiler=clang +``` -**Windows** +Visual Studio 2022 (MSVC) ```bash -premake\premake5.exe gmake2 # generate Makefiles (default: GCC) -premake\premake5.exe gmake2 --compiler=clang +premake\premake5.exe vs2022 +``` -premake\premake5.exe vs2022 # generate Visual Studio project (default: MSVC) +Visual Studio 2022 (Clang) +```bash premake\premake5.exe vs2022 --compiler=clang ``` -**Linux** +Visual Studio 2026 (MSVC) ```bash -./premake/premake5 gmake # generate Makefiles (default: GCC) +premake\premake5.exe vs2026 ``` -Enable tests in `pal_config.lua` by setting `PAL_BUILD_TESTS = true`. +Visual Studio 2026 (Clang) +```bash +premake\premake5.exe vs2026 --compiler=clang +``` ---- +### Linux +GNU Make (GCC): +```bash +./premake/premake5 gmake +``` -## Modules +GNU Make (Clang): +```bash +./premake/premake5 gmake --compiler=clang +``` -- `pal_core` - memory, log, time, version -- `pal_video` - windows, monitors, mouse, keyboard -- `pal_event` - event queue, event callback -- `pal_thread` - threads, synchronization -- `pal_opengl` - framebuffer configs, context +## Verify PAL ABI +If the ABI dump tool was enabled when geenrating the projects, build the project and run the command below to verify that your C99 compiler conforms to the PAL ABI. The command below use the release build. Replace **Release** with **Debug** if using debug build. -### Planned Modules -- `pal_graphics` - Vulkan, D3D12, Metal, Custom -- `pal_network` -- `pal_audio` -- `pal_hid` -- `pal_filesystem` +### Windows +```bash +cd bin +cd Release +abi-dump.exe --quick +``` + +### Linux +```bash +cd bin +cd Release +./abi-dump --quick +``` ---- +To view additional commands, run the abi dump tool with `--help`. ## Documentation - PAL uses [Doxygen](https://www.doxygen.nl/) for generating API documentation. ```bash @@ -166,14 +91,7 @@ doxygen doxyfile The generated HTML docs will be available in `docs/html/`. ---- - ## Contributing - -Contributions are welcome! Please open an issue or pull request. - ---- - -## License - -PAL is released under the [Zlib License](https://opensource.org/licenses/Zlib). +Contributions are welcome! Please open an issue or pull request. +See [CONTRIBUTING.md](./.github/CONTRIBUTING.md) for how and what to contribute. +Thanks for contributing to PAL. diff --git a/docs/Doxyfile b/docs/Doxyfile index c6446966..0c60d3c4 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -991,7 +991,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../include/pal +INPUT = ../include/pal homepage.md # This tag can be used to specify the character encoding of the source files # that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses @@ -1032,7 +1032,7 @@ INPUT_FILE_ENCODING = # *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h - + # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. @@ -1157,7 +1157,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the Doxygen output. -USE_MDFILE_AS_MAINPAGE = +USE_MDFILE_AS_MAINPAGE = homepage.md # If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- # directories of the project's root, is used as the documentation for that sub- diff --git a/docs/homepage.md b/docs/homepage.md new file mode 100644 index 00000000..bb0787cd --- /dev/null +++ b/docs/homepage.md @@ -0,0 +1,11 @@ + +# Prime Abstraction Layer + +PAL is a lightweight, low-level, explicit cross-platform abstraction layer in C over +platform and graphics APIs with support for modular builds and custom backends. +PAL is stateless and transparent. Queries return current state, +reflecting changes made through native API calls. + +PAL supports Windows, Linux, Vulkan and D3D12. Both Wayland and X11 are supported on Linux. + +PAL is released under the [Zlib License](https://opensource.org/licenses/Zlib). \ No newline at end of file diff --git a/include/pal/pal_config.h b/include/pal/pal_config.h deleted file mode 100644 index 65d5ee9e..00000000 --- a/include/pal/pal_config.h +++ /dev/null @@ -1,8 +0,0 @@ - -// Auto Generated Config Header From pal_config.lua -// Must not be edited manually - -#define PAL_HAS_SYSTEM 1 -#define PAL_HAS_THREAD 1 -#define PAL_HAS_VIDEO 1 -#define PAL_HAS_OPENGL 1 diff --git a/include/pal/pal_core.h b/include/pal/pal_core.h deleted file mode 100644 index a21c117a..00000000 --- a/include/pal/pal_core.h +++ /dev/null @@ -1,609 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -/** - * @defgroup pal_core Core - * Core PAL functionality such as versioning, memory allocation, logging, and - * timing. - * - * @{ - */ - -#ifndef _PAL_CORE_H -#define _PAL_CORE_H - -#include -#include - -#ifdef __cplusplus -#define PAL_EXTERN_C extern "C" -#else -#define PAL_EXTERN_C -#define nullptr ((void*)0) -#define true 1 -#define false 0 - -/** - * @brief A bool - * @since 1.0 - * @ingroup pal_core - */ -typedef _Bool bool; -#endif // __cplusplus - -// Set up shared library dependencies -#ifdef _WIN32 -#define PAL_CALL __stdcall -#ifdef _PAL_EXPORT -#define PAL_DECLSPEC PAL_EXTERN_C __declspec(dllexport) -#else -#define PAL_DECLSPEC PAL_EXTERN_C __declspec(dllimport) -#endif // PAL_EXPORT -#else -// other plafforms -#define PAL_CALL -#ifdef _PAL_EXPORT -#define PAL_DECLSPEC PAL_EXTERN_C __attribute__((visibility("default"))) -#else -#define PAL_DECLSPEC PAL_EXTERN_C -#endif // PAL_EXPORT -#endif // _WIN32 - -#ifdef _PAL_BUILD_DLL -#define PAL_API PAL_EXTERN_C PAL_DECLSPEC -#else -// static library -#define PAL_API PAL_EXTERN_C -#endif // _PAL_BUILD_DLL - -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define PAL_BIG_ENDIAN 1 -#else -#define PAL_BIG_ENDIAN 0 -#endif // __ORDER_BIG_ENDIAN__ - -#define PAL_BIT(x) 1 << x -#define PAL_BIT64(x) 1ULL << x - -/** - * @brief A signed 8-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef int8_t Int8; - -/** - * @brief A signed 16-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef int16_t Int16; - -/** - * @brief A signed 32-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef int32_t Int32; - -/** - * @brief A signed 64-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef int64_t Int64; - -/** - * @brief A signed 64-bit integer pointer - * @since 1.0 - * @ingroup pal_core - */ -typedef intptr_t IntPtr; - -/** - * @brief An unsigned 8-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef uint8_t Uint8; - -/** - * @brief An unsigned 16-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef uint16_t Uint16; - -/** - * @brief An unsigned 32-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef uint32_t Uint32; - -/** - * @brief An unsigned 64-bit integer - * @since 1.0 - * @ingroup pal_core - */ -typedef uint64_t Uint64; - -/** - * @brief An unsigned 64-bit integer pointer - * @since 1.0 - * @ingroup pal_core - */ -typedef uintptr_t UintPtr; - -/** - * @typedef PalAllocateFn - * @brief Function pointer type used for memory allocations. - * - * @param[in] userData Optional pointer to user data passed from ::PalAllocator. - * Can be nullptr. - * @param[in] size Number of bytes to allocate. - * @param[in] alignment Must be power of two. Set to 0 to use default. - * - * @return Pointer to the allocated memory on success or nullptr on failure. - * - * @since 1.0 - * @ingroup pal_core - * @sa PalFreeFn - */ -typedef void*(PAL_CALL* PalAllocateFn)( - void* userData, - Uint64 size, - Uint64 alignment); - -/** - * @typedef PalFreeFn - * @brief Function pointer type used for memory deallocations. - * - * @param[in] userData Optional pointer to user data passed from ::PalAllocator. - * Can be nullptr. - * @param[in] ptr Pointer to memory previously allocated by PalAllocateFn. - * - * @since 1.0 - * @ingroup pal_core - * @sa PalAllocateFn - */ -typedef void(PAL_CALL* PalFreeFn)( - void* userData, - void* ptr); - -/** - * @typedef PalLogCallback - * @brief Function pointer type used for log callbacks. - * - * @param userData Optional pointer to user data passed from ::PalLogger. Can be - * nullptr. - * @param msg Null-terminated UTF-8 log message. - * - * @since 1.0 - * @ingroup pal_core - * @sa palLog - */ -typedef void(PAL_CALL* PalLogCallback)( - void* userData, - const char* msg); - -/** - * @enum PalResult - * @brief Codes returned by most PAL functions. This is not a bitmask. - * - * All result codes follow the format `PAL_RESULT_**` for consistency and API - * use. - * - * @since 1.0 - * @ingroup pal_core - */ -typedef enum { - PAL_RESULT_SUCCESS, - PAL_RESULT_NULL_POINTER, - PAL_RESULT_INVALID_ARGUMENT, - PAL_RESULT_OUT_OF_MEMORY, - PAL_RESULT_PLATFORM_FAILURE, - PAL_RESULT_INVALID_ALLOCATOR, - PAL_RESULT_ACCESS_DENIED, - PAL_RESULT_TIMEOUT, - PAL_RESULT_INSUFFICIENT_BUFFER, - PAL_RESULT_INVALID_THREAD, - PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED, - PAL_RESULT_VIDEO_NOT_INITIALIZED, - PAL_RESULT_INVALID_MONITOR, - PAL_RESULT_INVALID_MONITOR_MODE, - PAL_RESULT_INVALID_WINDOW, - PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED, - PAL_RESULT_INVALID_KEYCODE, - PAL_RESULT_INVALID_SCANCODE, - PAL_RESULT_INVALID_MOUSE_BUTTON, - PAL_RESULT_INVALID_ORIENTATION, - PAL_RESULT_GL_NOT_INITIALIZED, - PAL_RESULT_INVALID_GL_WINDOW, - PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED, - PAL_RESULT_INVALID_GL_FBCONFIG, - PAL_RESULT_INVALID_GL_VERSION, - PAL_RESULT_INVALID_GL_PROFILE, - PAL_RESULT_INVALID_GL_CONTEXT, - PAL_RESULT_INVALID_FBCONFIG_BACKEND -} PalResult; - -/** - * @struct PalVersion - * @brief Describes the version of PAL. - * - * @since 1.0 - * @ingroup pal_core - */ -typedef struct { - Uint32 major; /**< Major version (breaking changes).*/ - Uint32 minor; /**< Minor version (adding features).*/ - Uint32 build; /**< Build version (bug fixes).*/ -} PalVersion; - -/** - * @struct PalAllocator - * @brief Custom memory allocator. - * - * Provides user-defined memory allocation and free functions. - * - * @since 1.0 - * @ingroup pal_core - */ -typedef struct { - PalAllocateFn allocate; - PalFreeFn free; - void* userData; /**< Optional user-provided data. Can be nullptr.*/ -} PalAllocator; - -/** - * @struct PalLogger - * @brief Logging configuration. - * - * Provides a callback and user data for handling log messages. - * - * @since 1.0 - * @ingroup pal_core - */ -typedef struct { - PalLogCallback callback; - void* userData; /** Optional user-provided data. Can be nullptr.*/ -} PalLogger; - -/** - * Retrieve the PAL version number. - * - * @return PAL version (major, minor, build). - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palGetVersionString - */ -PAL_API PalVersion PAL_CALL palGetVersion(); - -/** - * Retrieve the PAL version string. - * - * @return Null-terminated string containing the PAL version. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palGetVersion - */ -PAL_API const char* PAL_CALL palGetVersionString(); - -/** - * Convert a result code to a human-readable string. - * - * @param result The PalResult code to format. - * - * @return Null-terminated static string describing the result. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - */ -PAL_API const char* PAL_CALL palFormatResult(PalResult result); - -/** - * Allocate memory using the provided allocator. - * - * @param allocator The allocator to use. Set to nullptr to use default. - * @param size Number of bytes to allocate. - * @param alignment Alignment in bytes. Must be a power of two. - * - * @return Pointer to allocated memory on success, or nullptr on failure. - * - * Thread safety: Thread safe only if the provided allocator is thread - * safe. The default allocator is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palFree - */ -PAL_API void* PAL_CALL palAllocate( - const PalAllocator* allocator, - Uint64 size, - Uint64 alignment); - -/** - * Free memory allocated by palAllocate. - * - * @param allocator The allocator used to allocate the memory. Set to nullptr to - * use default. - * @param ptr Pointer to memory to free. If nullptr, the function returns - * silently. - * - * Thread safety: Thread safe only if the provided allocator is thread - * safe. The default allocator is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palAllocate - */ -PAL_API void PAL_CALL palFree( - const PalAllocator* allocator, - void* ptr); - -/** - * Log a formatted message. - * - * @param logger Logger instance. Must not be NULL. - * @param fmt printf-style format string. - * @param ... Arguments for the format string. - * - * Thread safety: This function is thread safe, but log output and - * callbacks may be invoked concurrently. The user must ensure the callback - * implementation is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palFormatResult - */ -PAL_API void PAL_CALL palLog( - const PalLogger* logger, - const char* fmt, - ...); - -/** - * Query a high-resolution performance counter value. - * - * @return Current performance counter value. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palGetPerformanceFrequency - */ -PAL_API Uint64 PAL_CALL palGetPerformanceCounter(); - -/** - * Query the frequency of the high-resolution performance counter. - * - * @return Performance counter frequency, in counts per second. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palGetPerformanceCounter - */ -PAL_API Uint64 PAL_CALL palGetPerformanceFrequency(); - -/** - * @brief Combine two 32-bit unsigned integers into a single 64-bit signed - * integer. - * - * @return The combined 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palUnpackUint32 - */ -static inline Int64 PAL_CALL palPackUint32( - Uint32 low, - Uint32 high) -{ - return (Int64)(((Uint64)high << 32) | (Uint64)low); -} - -/** - * @brief Combine two 32-bit signed integers into a single 64-bit signed - * integer. - * - * @return The combined 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palUnpackInt32 - */ -static inline Int64 PAL_CALL palPackInt32( - Int32 low, - Int32 high) -{ - return ((Int64)(Uint32)high << 32) | (Uint32)low; -} - -/** - * @brief Pack a pointer into a 64-bit signed integer. - * - * @return The packed 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palUnpackPointer - */ -static inline Int64 PAL_CALL palPackPointer(void* ptr) -{ - return (Int64)(UintPtr)ptr; -} - -/** - * @brief Combine two floats into a single 64-bit signed integer. - * - * @return The combined 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.3 - * @ingroup pal_core - * @sa palUnpackFloat - */ -static inline Int64 PAL_CALL palPackFloat( - float low, - float high) -{ - Int64 combined = 0; -#if PAL_BIG_ENDIAN - memcpy(&((Uint32*)&combined)[0], &high, sizeof(float)); - memcpy(&((Uint32*)&combined)[1], &low, sizeof(float)); -#else - memcpy(&((Uint32*)&combined)[0], &low, sizeof(float)); - memcpy(&((Uint32*)&combined)[1], &high, sizeof(float)); -#endif // PAL_BIG_ENDIAN - - return combined; -} - -/** - * @brief Retrieve two 32-bit unsigned integers from a 64-bit signed integer. - * - * @param[out] outLow Low value of the 64-bit signed integer. - * @param[out] outHigh High value of the 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palPackUint32 - */ -static inline void PAL_CALL palUnpackUint32( - Int64 data, - Uint32* outLow, - Uint32* outHigh) -{ - if (outLow) { - *outLow = (Uint32)(data & 0xFFFFFFFF); - } - - if (outHigh) { - *outHigh = (Uint32)((Uint64)data >> 32); - } -} - -/** - * @brief Retrieve two 32-bit signed integers from a 64-bit signed integer. - * - * @param[out] outLow Low value of the 64-bit signed integer. - * @param[out] outHigh High value of the 64-bit signed integer. - * - * Thread safety: This function is thread-safe if `outLow` and `outHigh` are - * thread local. - * - * @since 1.0 - * @ingroup pal_core - * @sa palPackInt32 - */ -static inline void PAL_CALL palUnpackInt32( - Int64 data, - Int32* outLow, - Int32* outHigh) -{ - if (outLow) { - *outLow = (Int32)(data & 0xFFFFFFFF); - } - - if (outHigh) { - *outHigh = (Int32)((Uint64)data >> 32); - } -} - -/** - * @brief Unpack a pointer from a 64-bit signed integer. - * - * @return The pointer from the 64-bit signed integer. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_core - * @sa palPackPointer - */ -static inline void* PAL_CALL palUnpackPointer(Int64 data) -{ - return (void*)(UintPtr)data; -} - -/** - * @brief Retrieve two floats from a 64-bit signed integer. - * - * @param[out] outLow Low value of the 64-bit signed integer. - * @param[out] outHigh High value of the 64-bit signed integer. - * - * Thread safety: This function is thread-safe if `outLow` and `outHigh` are - * thread local. - * - * @since 1.3 - * @ingroup pal_core - * @sa palPackFloat - */ -static inline void PAL_CALL palUnpackFloat( - Int64 data, - float* low, - float* high) -{ -#if PAL_BIG_ENDIAN - if (low) { - memcpy(low, &((Uint32*)&data)[1], sizeof(float)); - } - - if (high) { - memcpy(high, &((Uint32*)&data)[0], sizeof(float)); - } -#else - if (low) { - memcpy(low, &((Uint32*)&data)[0], sizeof(float)); - } - - if (high) { - memcpy(high, &((Uint32*)&data)[1], sizeof(float)); - } - -#endif // PAL_BIG_ENDIAN -} - -/** @} */ // end of pal_core group - -#endif // _PAL_CORE_H \ No newline at end of file diff --git a/include/pal/pal_event.h b/include/pal/pal_event.h deleted file mode 100644 index f85a7b05..00000000 --- a/include/pal/pal_event.h +++ /dev/null @@ -1,588 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -/** - * @defgroup pal_event Event - * Event PAL functionality such as queues, event drivers, and event callbacks. - * - * @{ - */ - -#ifndef _PAL_EVENT_H -#define _PAL_EVENT_H - -#include "pal_core.h" - -/** - * @struct PalEventDriver - * @brief Opaque handle to an event driver. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef struct PalEventDriver PalEventDriver; - -/** - * @struct PalEvent - * @brief A single event. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef struct PalEvent PalEvent; - -/** - * @typedef PalEventCallback - * @brief Function pointer type used for event callbacks. - * - * @param[in] userData Optional pointer to user data. Can be nullptr. - * @param[in] event The event. - * - * @since 1.0 - * @ingroup pal_event - * @sa PalPushFn - */ -typedef void(PAL_CALL* PalEventCallback)( - void* userData, - const PalEvent* event); - -/** - * @typedef PalPushFn - * @brief Function pointer type used for pushing events into event queues. - * - * @param[in] userData Optional pointer to user data. Can be nullptr. - * @param[in] event The event to push. - * - * @since 1.0 - * @ingroup pal_event - * @sa PalEventCallback - */ -typedef void(PAL_CALL* PalPushFn)( - void* userData, - PalEvent* event); - -/** - * @typedef PalPollFn - * @brief Function pointer type used for polling events from event queues. - * - * This function should return false if the event queue is empty. - * If the queue is not empty and the event was retrieved, this should return - * true. - * - * @param[in] userData Optional pointer to user data. Can be nullptr. - * @param[out] event Pointer to a PalEvent to recieve the event. - * - * @since 1.0 - * @ingroup pal_event - * @sa PalPushFn - */ -typedef bool(PAL_CALL* PalPollFn)( - void* userData, - PalEvent* outEvent); - -/** - * @enum PalDecorationMode - * @brief Decoration types. This is not a bitmask enum. - * - * All decoration types follow the format `PAL_DECORATION_MODE_**` for - * consistency and API use. - * - * @since 1.3 - * @ingroup pal_event - */ -typedef enum { - PAL_DECORATION_MODE_CLIENT_SIDE, - PAL_DECORATION_MODE_SERVER_SIDE -} PalDecorationMode; - -/** - * @enum PalEventType - * @brief Event types. This is not a bitmask enum. - * - * All event types follow the format `PAL_EVENT_**` for consistency and - * API use. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef enum { - /** - * PAL_EVENT_WINDOW_CLOSE - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_CLOSE, - - /** - * PAL_EVENT_WINDOW_SIZE - * - * event.data : lower 32 bits = width, upper 32 bits = height - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackUint32() - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_SIZE, - - /** - * PAL_EVENT_WINDOW_MOVE - * - * event.data : lower 32 bits = x, upper 32 bits = y - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackInt32() - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_MOVE, - - /** - * PAL_EVENT_WINDOW_STATE - * - * event.data : state(minimized, maximized, restored). - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_STATE, - - /** - * PAL_EVENT_WINDOW_FOCUS - * - * event.data : `true` for focus gained or `false` for focus lost. - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_FOCUS, - - /** - * PAL_EVENT_WINDOW_VISIBILITY - * - * event.data : `true` for visible or `false` for hidden. - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_VISIBILITY, - - /** - * @brief WM_ENTERSIZEMOVE (Windows Only). - * - * PAL_EVENT_WINDOW_MODAL_BEGIN - * - * event.data2 : window - */ - PAL_EVENT_WINDOW_MODAL_BEGIN, - - /** - * @brief WM_EXITSIZEMOVE (Windows Only). - * - * PAL_EVENT_WINDOW_MODAL_END - * - * event.data2 : window - */ - PAL_EVENT_WINDOW_MODAL_END, - - /** - * PAL_EVENT_MONITOR_DPI_CHANGED - * - * event.data2 : window - */ - PAL_EVENT_MONITOR_DPI_CHANGED, - - /** - * @brief Monitor list changed - * - * PAL_EVENT_MONITOR_LIST_CHANGED - * - * event.data2 : window - */ - PAL_EVENT_MONITOR_LIST_CHANGED, - - /** - * PAL_EVENT_KEYDOWN - * - * event.data : lower 32 bits = keycode, upper 32 bits = scancode - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackUint32() - * - palUnpackPointer() - */ - PAL_EVENT_KEYDOWN, - - /** - * PAL_EVENT_KEYREPEAT - * - * event.data : lower 32 bits = keycode, upper 32 bits = scancode - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackUint32() - * - palUnpackPointer() - */ - PAL_EVENT_KEYREPEAT, - - /** - * PAL_EVENT_KEYUP - * - * event.data : lower 32 bits = keycode, upper 32 bits = scancode - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackUint32() - * - palUnpackPointer() - */ - PAL_EVENT_KEYUP, - - /** - * PAL_EVENT_MOUSE_BUTTONDOWN - * - * event.data : lower 32 bits = button, upper 32 bits = serial - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_MOUSE_BUTTONDOWN, - - /** - * PAL_EVENT_MOUSE_BUTTONUP - * - * event.data : lower 32 bits = button, upper 32 bits = serial - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_MOUSE_BUTTONUP, - - /** - * PAL_EVENT_MOUSE_MOVE - * - * event.data : lower 32 bits = x, upper 32 bits = y - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackInt32() - * - palUnpackPointer() - */ - PAL_EVENT_MOUSE_MOVE, - - /** - * @brief Mouse movement delta. - * - * PAL_EVENT_MOUSE_DELTA - * - * event.data : lower 32 bits = dx, upper 32 bits = dy - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackInt32() - * - palUnpackPointer() - */ - PAL_EVENT_MOUSE_DELTA, - - /** - * PAL_EVENT_MOUSE_WHEEL - * - * event.data : lower 32 bits = dx, upper 32 bits = dy - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackInt32() - * - palUnpackPointer() - */ - PAL_EVENT_MOUSE_WHEEL, - - /** - * PAL_EVENT_USER - * - * event.userId : User event ID or type. - * - * Use inline helpers: - * - palPackInt32() - * - palPackUint32() - * - palPackPointer() - * - palUnpackInt32() - * - palUnpackUint32() - * - palUnpackPointer() - */ - PAL_EVENT_USER, - - /** - * PAL_EVENT_USER - * - * event.data : codepoint - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_KEYCHAR, - - /** - * PAL_EVENT_WINDOW_DECORATION_MODE - * - * event.data : negotiated decorations mode - * - * event.data2 : window - * - * Use inline helpers: - * - palUnpackPointer() - */ - PAL_EVENT_WINDOW_DECORATION_MODE, - - PAL_EVENT_MAX -} PalEventType; - -/** - * @enum PalDispatchMode - * @brief Dispatch types for an event. This is not a bitmask enum. - * - * All dispatch modes follow the format `PAL_DISPATCH_**` for consistency - * and API use. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef enum { - PAL_DISPATCH_NONE, /**< No dispatch.*/ - PAL_DISPATCH_CALLBACK, /**< Dispatch to event callback.*/ - PAL_DISPATCH_POLL, /**< Dispatch to the event queue.*/ - PAL_DISPATCH_MAX -} PalDispatchMode; - -struct PalEvent { - PalEventType type; - Int64 data; /**< First data payload.*/ - Int64 data2; /**< Second data payload.*/ - Int64 userId; /**< You can have user events upto Int64 max.*/ -}; - -/** - * @struct PalEventQueue - * @brief Custom event queue. - * - * Provides user-defined event queue push and poll functions. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef struct { - PalPushFn push; - PalPollFn poll; - void* userData; /**< Optional user-provided data. Can be nullptr.*/ -} PalEventQueue; - -/** - * @struct PalEventDriverCreateInfo - * @brief Creation parameters for an event driver. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_event - */ -typedef struct { - const PalAllocator* allocator; /**< Set to nullptr to use default.*/ - PalEventQueue* queue; /**< Set to nullptr to use default.*/ - PalEventCallback callback; /**< Can be nullptr.*/ - void* userData; /**< Optional user-provided data. Can be nullptr.*/ -} PalEventDriverCreateInfo; - -/** - * @brief Create an event driver. - * - * The allocator field in the provided PalEventDriverCreateInfo struct will not - * be copied, therefore the pointer must remain valid until the event driver is - * destroyed. Destroy the event driver with palDestroyEventDriver() when no - * longer needed. - * - * @param[in] info Pointer to a PalEventDriverCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outEventDriver Pointer to a PalEventDriver to recieve the created - * event driver. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function is thread safe if the provided allocator is - * thread safe and `outEventDriver` is thread local. The default allocator is - * thread safe. - * - * @since 1.0 - * @ingroup pal_event - * @sa palDestroyEventDriver - */ -PAL_API PalResult PAL_CALL palCreateEventDriver( - const PalEventDriverCreateInfo* info, - PalEventDriver** outEventDriver); - -/** - * @brief Destroy the provided event driver. - * - * If the provided event driver is invalid or nullptr, this function returns - * silently. - * - * @param[in] eventDriver Pointer to the event driver to destroy. - * - * Thread safety: This function is thread safe if the allocator used to create - * the event driver is thread safe and `eventDriver` is thread local. - * - * @since 1.0 - * @ingroup pal_event - * @sa palCreateEventDriver - */ -PAL_API void PAL_CALL palDestroyEventDriver(PalEventDriver* eventDriver); - -/** - * @brief Set the dispatch mode for an event type with the provided event - * driver. - * - * If the provided event driver is invalid or nullptr, this function returns - * silently. - * - * If the dispatch mode is `PAL_DISPATCH_POLL`, the event will be dispatched - * into the event drivers event queue. If the dispatch mode is - * `PAL_DISPATCH_CALLBACK` and the event driver has a valid callback function, - * the event will be dispatched to the callback function of the event driver - * otherwise the event will be discarded. - * - * @param[in] eventDriver Pointer to the event driver. - * @param[in] type Event type to set dispatch mode for. - * @param[in] mode Dispatch mode to use. - * - * Thread safety: This function is thread if multiple threads are not - * simultaneously setting dispatch mode on the same `eventDriver`. - * - * @since 1.0 - * @ingroup pal_event - * @sa palGetEventDispatchMode - */ -PAL_API void PAL_CALL palSetEventDispatchMode( - PalEventDriver* eventDriver, - PalEventType type, - PalDispatchMode mode); - -/** - * @brief Get the dispatch mode for an event type with the provided event - * driver. - * - * @param[in] eventDriver Pointer to the event driver. - * @param[in] type The event type. - * - * @return The dispatch mode on success or `PAL_DISPATCH_NONE` on failure. - * - * Thread safety: This function is thread if multiple threads are not - * simultaneously setting dispatch mode on the same `eventDriver`. - * - * @since 1.0 - * @ingroup pal_event - * @sa palSetEventDispatchMode - */ -PAL_API PalDispatchMode PAL_CALL palGetEventDispatchMode( - PalEventDriver* eventDriver, - PalEventType type); - -/** - * @brief Push an event into the queue or callback function of the provided - * event driver. - * - * If the provided event driver is invalid or nullptr, this function returns - * silently. - * - * If the dispatch mode for the event is `PAL_DISPATCH_POLL`, the event will be - * pushed to the event queue. - * - * If dispatch mode is `PAL_DISPATCH_CALLBACK` and the event driver has a valid - * event callback, the callback will be called otherwise the event will be - * discarded. - * - * @param[in] eventDriver Pointer to the event driver. - * @param[in] event Pointer to the event to push. - * - * Thread safety: This function is thread if the provided event queue is thread - * safe or every thread has its own `eventDriver`. The default event queue is - * not thread safe. - * - * @since 1.0 - * @ingroup pal_event - * @sa palPollEvent - */ -PAL_API void PAL_CALL palPushEvent( - PalEventDriver* eventDriver, - PalEvent* event); - -/** - * @brief Retrieve the next available event from the queue of the provided event - * driver. - * - * If the provided event driver is invalid or nullptr, this function returns - * silently. - * - * This function retrieves the next pending event from the queue of the - * provided event driver without blocking. If no events are available, it - * returns false. - * - * @param[in] eventDriver Pointer to the event driver. - * @param[out] outEvent Pointer to a PalEvent to recieve the event. Must be - * valid. - * - * Thread safety: This function is thread if the provided event queue is thread - * safe or every thread has its own `eventDriver`. The default event queue is - * not thread safe. - * - * @since 1.0 - * @ingroup pal_event - * @sa palPushEvent - */ -PAL_API bool PAL_CALL palPollEvent( - PalEventDriver* eventDriver, - PalEvent* outEvent); - -/** @} */ // end of pal_event group - -#endif // _PAL_EVENT_H \ No newline at end of file diff --git a/include/pal/pal_opengl.h b/include/pal/pal_opengl.h deleted file mode 100644 index a54a5e79..00000000 --- a/include/pal/pal_opengl.h +++ /dev/null @@ -1,488 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -/** - * @defgroup pal_opengl Opengl - * Opengl PAL functionality such as FBConfigs and contexts. - * - * @{ - */ - -#ifndef _PAL_OPENGL_H -#define _PAL_OPENGL_H - -#include "pal_core.h" - -// Opengl API entry -#ifdef _WIN32 -#define PAL_GL_APIENTRY __stdcall -#else -#define PAL_GL_APIENTRY -#endif // _WIN32 - -/** - * @struct PalGLContext - * @brief Opaque handle to an opengl context. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef struct PalGLContext PalGLContext; - -/** - * @enum PalGLExtensions - * @brief Opengl system extensions. - * - * All opengl extensions follow the format `PAL_GL_EXTENSION_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef enum { - PAL_GL_EXTENSION_CREATE_CONTEXT = PAL_BIT(0), /**< Modern context.*/ - PAL_GL_EXTENSION_CONTEXT_PROFILE = PAL_BIT(1), - PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2 = PAL_BIT(2), - PAL_GL_EXTENSION_ROBUSTNESS = PAL_BIT(3), /**< Reset behaviour.*/ - PAL_GL_EXTENSION_NO_ERROR = PAL_BIT(4), /**< No errors*/ - PAL_GL_EXTENSION_PIXEL_FORMAT = PAL_BIT(5), /**< Modern PalGLFBConfig.*/ - PAL_GL_EXTENSION_MULTISAMPLE = PAL_BIT(6), /**< Multisample FBConfigs.*/ - PAL_GL_EXTENSION_SWAP_CONTROL = PAL_BIT(7), /**< Vsync.*/ - PAL_GL_EXTENSION_FLUSH_CONTROL = PAL_BIT(8), /**< Release behavior.*/ - PAL_GL_EXTENSION_COLORSPACE_SRGB = PAL_BIT(9) /**< Standard RGB.*/ -} PalGLExtensions; - -/** - * @enum PalGLProfile - * @brief Opengl context creation profiles. This is not a bitmask. - * - * All opengl profiles follow the format `PAL_GL_PROFILE_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef enum { - PAL_GL_PROFILE_NONE, /**< Default profile.*/ - PAL_GL_PROFILE_CORE, /**< PAL_GL_EXTENSION_CONTEXT_PROFILE.*/ - PAL_GL_PROFILE_COMPATIBILITY, /**< PAL_GL_EXTENSION_CONTEXT_PROFILE.*/ - PAL_GL_PROFILE_ES /**< PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2.*/ -} PalGLProfile; - -/** - * @enum PalGLContextReset - * @brief Opengl context reset behavior. This is not a bitmask. - * - * All context reset behavior follow the format `PAL_GL_CONTEXT_RESET_**` - * for consistency and API use. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef enum { - PAL_GL_CONTEXT_RESET_NONE, /**< Default reset behaviour.*/ - PAL_GL_CONTEXT_RESET_NO_NOTIFICATION, /**< PAL_GL_EXTENSION_ROBUSTNESS.*/ - PAL_GL_CONTEXT_RESET_LOSE_CONTEXT /**< PAL_GL_EXTENSION_ROBUSTNESS.*/ -} PalGLContextReset; - -/** - * @enum PalGLRelease - * @brief Opengl context release behavior. This is not a bitmask. - * - * All opengl context release behavior follow the format - * `PAL_GL_RELEASE_BEHAVIOR_**` for consistency and API use. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef enum { - PAL_GL_RELEASE_BEHAVIOR_NONE, /**< Default release behaviour..*/ - PAL_GL_RELEASE_BEHAVIOR_FLUSH /**< PAL_GL_EXTENSION_FLUSH_CONTROL.*/ -} PalGLRelease; - -/** - * @struct PalGLInfo - * @brief Information about the opengl driver. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef struct { - PalGLExtensions extensions; - Uint32 major; /**< Version major.*/ - Uint32 minor; /**< Version minor.*/ - char vendor[32]; /**< Graphics card vendor name (eg. Intel).*/ - char graphicsCard[64]; /**< Graphics card name.*/ - char version[64]; /**< Version string (major, minor, build).*/ -} PalGLInfo; - -/** - * @struct PalGLFBConfig - * @brief Information about an opengl framebuffer. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef struct { - bool doubleBuffer; - bool stereo; - bool sRGB; - Uint16 index; /**< Driver index. Must not be changed.*/ - Uint16 redBits; - Uint16 greenBits; - Uint16 blueBits; - Uint16 alphaBits; - Uint16 depthBits; - Uint16 stencilBits; - Uint16 samples; /**< PAL_GL_EXTENSION_MULTISAMPLE or 1.*/ -} PalGLFBConfig; - -/** - * @struct PalGLWindow - * @brief Information about an opengl window. - * - * This can be allocated statically or dynamically since its used for - * holding native handles. The handles will not be copied. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef struct { - void* display; /**< Can be nullptr depending on platform (eg. Windows).*/ - void* window; /**< Must not be nullptr. (egl_wl_window on Wayland)*/ -} PalGLWindow; - -/** - * @struct PalGLContextCreateInfo - * @brief Creation parameters for an opengl context. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_opengl - */ -typedef struct { - bool forward; /**< Forward compatible context.*/ - bool noError; /**< No error context.*/ - bool debug; /**< Debug context. */ - Uint16 major; /** major version.*/ - Uint16 minor; /** minor version.*/ - PalGLProfile profile; /**< see PalGLProfile.*/ - PalGLContextReset reset; /**< see PalGLContextReset.*/ - PalGLRelease release; /**< see PalGLRelease.*/ - PalGLContext* shareContext; /**< Can be nullptr.*/ - const PalGLFBConfig* fbConfig; /**< Must not be nullptr.*/ - const PalGLWindow* window; /** Must not be nullptr.*/ -} PalGLContextCreateInfo; - -/** - * @brief Initialize the opengl system. - * - * This must be called before any opengl function. The opengl system must be - * shutdown with palShutdownGL() when no longer needed. - * - * The allocator will not not copied, therefore the pointer must remain valid - * until the opengl system is shutdown. - * - * @param[in] allocator Optional user-provided allocator. Set to nullptr to use - * default. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palShutdownGL - * @sa palGLSetInstance - */ -PAL_API PalResult PAL_CALL palInitGL(const PalAllocator* allocator); - -/** - * @brief Shutdown the opengl system. - * - * If the opengl system has not been initialized, the function returns silently. - * All created contexts must be destroyed before this call. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palInitGL - */ -PAL_API void PAL_CALL palShutdownGL(); - -/** - * @brief Get information about the opengl driver. - * - * The opengl system must be initialized before this call. The returned - * PalGLInfo pointer must not be freed. - * - * @return A pointer to a PalGLInfo on success or nullptr on failure. - * - * Thread safety: This function is thread-safe. - * - * @since 1.0 - * @ingroup pal_opengl - */ -PAL_API const PalGLInfo* PAL_CALL palGetGLInfo(); - -/** - * @brief Return a list of all supported PalGLFBConfig on the provided window. - * - * The opengl system must be initialized before this call. - * - * Call this function first with PalGLFBConfig array set to nullptr to get the - * number of supported PalGLFBConfig. Allocate memory for the PalGLFBConfig - * array and passed in the count and the allocated array. If the count of the - * array is less than the number of supported PalGLFBConfigs, PAL will write - * upto that limit. - * - * If the count is 0 and the PalGLFBConfigs array is nullptr, the function fails - * and returns `PAL_RESULT_INSUFFICIENT_BUFFER`. - * - * @param[in] glWindow Set to nullptr. - * @param[in] count Capacity of the PalGLFBConfig array. - * @param[out] configs User allocated array of PalGLFBConfig. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palInitGL - */ -PAL_API PalResult PAL_CALL palEnumerateGLFBConfigs( - PalGLWindow* glWindow, - Int32* count, - PalGLFBConfig* configs); - -/** - * @brief Get the closest PalGLFBConfig from the array of PalGLFBConfig with a - * desired PalGLFBConfig. - * - * The opengl system must be initialized before this call. - * This function uses missing and score system to get the closest PalGLFBConfig. - * - * The count must be the number of PalGLFBConfig in the - * array of PalGLFBConfig. If the count is less than or equal to 0 or the - * desired PalGLFBConfig or the PalGLFBConfig array is nullptr, this function - * fails and returns nullptr. - * - * @param[in] configs Pointer to the array of PalGLFBConfig. - * @param[in] count Capacity of the PalGLFBConfig array. - * @param[in] desired The desired PalGLFBConfig. - * - * @return The closest PalGLFBConfig on success or nullptr on failure. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_opengl - */ -PAL_API const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( - PalGLFBConfig* configs, - Int32 count, - const PalGLFBConfig* desired); - -/** - * @brief Creates an opengl context. - * - * The opengl system must be initialized before this call. The created context - * will not be made current. - * - * The provided PalGLFBConfig must be the same as the one used to create the - * window. Once set, it cannot be changed. To change it, you must destroy the - * window and recreate it. - * - * On Wayland: PalGLContextCreateInfo::PalGLWindow::window is the wl_egl_window - * not the wl_surface. - * - * @param[in] info Pointer to a PalGLContextCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outContext Pointer to a PalGLContext to recieve the created - * context. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palDestroyGLContext - */ -PAL_API PalResult PAL_CALL palCreateGLContext( - const PalGLContextCreateInfo* info, - PalGLContext** outContext); - -/** - * @brief Destroy the provided opengl context. - * - * The opengl system must be initialized before this call. - * - * If the provided context is invalid or nullptr, this function returns - * silently. The context must not be current in any thread before this call. - * - * @param[in] context Pointer to the context to destroy. - * - * Thread safety: This function is thread safe if the `context` is thread local. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palCreateGLContext - */ -PAL_API void PAL_CALL palDestroyGLContext(PalGLContext* context); - -/** - * @brief Make the provided context current on the calling thread. - * - * The opengl system must be initialized before this call. - * - * The opengl window must have the same PalGLFBConfig used to create the - * context. If the PalGLFBConfig of the opengl window is not the same as the one - * used to create the context, this function fails and returns - * `PAL_RESULT_INVALID_GL_WINDOW`. - * - * If the window was created with a different display other than the one - * passed to the opengl system, this function fails and returns - * `PAL_RESULT_INVALID_GL_WINDOW`. see palGLSetInstance() - * - * @param[in] glWindow Pointer to the opengl window. - * @param[in] context Pointer to the context to make current. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function is thread safe, but only one thread may have the - * current context at a time. - * - * @since 1.0 - * @ingroup pal_opengl - */ -PAL_API PalResult PAL_CALL palMakeContextCurrent( - PalGLWindow* glWindow, - PalGLContext* context); - -/** - * @brief Get the pointer to a named opengl function. - * - * The opengl system must be initialized before this call. - * - * @param[in] name UTF-8 string for the function name. - * - * @return the pointer to the function on success or nullptr on failure. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palInitGL - */ -PAL_API void* PAL_CALL palGLGetProcAddress(const char* name); - -/** - * @brief Present the contents of the back buffer of the provided context to - * the screen. - * - * The opengl system must be initialized before this call. - * - * @param[in] glWindow Pointer to the opengl window. - * @param[in] context Pointer to the context. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from a thread that has a - * bound context. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palMakeContextCurrent - */ -PAL_API PalResult PAL_CALL palSwapBuffers( - PalGLWindow* glWindow, - PalGLContext* context); - -/** - * @brief Set the swap interval for the current context. - * - * The opengl system must be initialized before this call. - * This affects the currently bound context on the calling thread. - * `PAL_GL_EXTENSION_SWAP_CONTROL` must be supported. Set interval to 1 for - * vsync. - * - * @param[in] interval The swap interval - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from a thread with a bound - * context. - * - * @since 1.0 - * @ingroup pal_opengl - * @sa palMakeContextCurrent - */ -PAL_API PalResult PAL_CALL palSetSwapInterval(Int32 interval); - -/** - * @brief Set the native application instance or display for the opengl system. - * - * This must be called before palInitGL() is called. if palInitGL() is called - * before this function, - * it fails and returns `PAL_RESULT_PLATFORM_FAILURE` will be returned. - * - * On Linux: This is the Display associated with the connection. - - * On Windows: This is the HINSTANCE of the process. - * - * Thread safety: This function is thread safe. - * - * @note The provided instance will not be freed by the opengl system. - * - * @since 1.3 - * @ingroup pal_opengl - * @sa palInitGL - */ -PAL_API void PAL_CALL palGLSetInstance(void* instance); - -/** - * @brief Get the backend of the opengl system. - * - * The opengl system must be initialized before this call. - * Possible values are `wgl`, `glx`, `gles`, `egl`. - * - * Thread safety: This function is thread safe. - * - * @since 1.3 - * @ingroup pal_opengl - */ -PAL_API const char* PAL_CALL palGLGetBackend(); - -/** @} */ // end of pal_opengl group - -#endif // _PAL_OPENGL_H \ No newline at end of file diff --git a/include/pal/pal_system.h b/include/pal/pal_system.h deleted file mode 100644 index 9e671535..00000000 --- a/include/pal/pal_system.h +++ /dev/null @@ -1,201 +0,0 @@ -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -/** - * @defgroup pal_system System - * System PAL functionality such as CPU info and Platform info. - * - * @{ - */ - -#ifndef _PAL_SYSTEM_H -#define _PAL_SYSTEM_H - -#include "pal_core.h" - -#define PAL_PLATFORM_NAME_SIZE 32 -#define PAL_CPU_VENDOR_NAME_SIZE 16 -#define PAL_CPU_MODEL_NAME_SIZE 64 - -/** - * @enum PalCpuArch - * @brief CPU achitecture. This is not a bitmask. - * - * This is a build time achitecture. Example: Generating your project with x64 - * will reflect PAL_CPU_ARCH_X86_64. - * - * All CPU achitectures follow the format `PAL_CPU_ARCH_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_system - */ -typedef enum { - PAL_CPU_ARCH_UNKNOWN, - PAL_CPU_ARCH_X86, - PAL_CPU_ARCH_X86_64, - PAL_CPU_ARCH_ARM, - PAL_CPU_ARCH_ARM64 -} PalCpuArch; - -/** - * @enum PalCpuFeatures - * @brief CPU features (instruction sets). - * - * All CPU features sets follow the format `PAL_CPU_FEATURE_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_system - */ -typedef enum { - PAL_CPU_FEATURE_SSE = PAL_BIT(0), - PAL_CPU_FEATURE_SSE2 = PAL_BIT(1), - PAL_CPU_FEATURE_SSE3 = PAL_BIT(2), - PAL_CPU_FEATURE_SSSE3 = PAL_BIT(3), - PAL_CPU_FEATURE_SSE41 = PAL_BIT(4), /**< SSE4.1.*/ - PAL_CPU_FEATURE_SSE42 = PAL_BIT(5), /**< SSE4.1.*/ - PAL_CPU_FEATURE_AVX = PAL_BIT(6), - PAL_CPU_FEATURE_AVX2 = PAL_BIT(7), - PAL_CPU_FEATURE_AVX512F = PAL_BIT(8), - PAL_CPU_FEATURE_FMA3 = PAL_BIT(9), - PAL_CPU_FEATURE_BMI1 = PAL_BIT(10), - PAL_CPU_FEATURE_BMI2 = PAL_BIT(11) -} PalCpuFeatures; - -/** - * @enum PalPlatformType - * @brief Platform types. This is not a bitmask. - * - * This is the family name (eg. This does not show if its Windows 7 or Windows 8 - * etc). - * - * All platform types follow the format `PAL_PLATFORM_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_system - */ -typedef enum { - PAL_PLATFORM_WINDOWS, - PAL_PLATFORM_LINUX, - PAL_PLATFORM_MACOS, - PAL_PLATFORM_ANDROID, - PAL_PLATFORM_IOS -} PalPlatformType; - -/** - * @enum PalPlatformApiType - * @brief Platform API types. This is not a bitmask. - * - * This is the API the playform uses. Most platforms support only one (eg. - * Windows). - * - * All platform API types follow the format `PAL_PLATFORM_API_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_system - */ -typedef enum { - PAL_PLATFORM_API_WIN32, - PAL_PLATFORM_API_WAYLAND, - PAL_PLATFORM_API_X11, - PAL_PLATFORM_API_COCOA, - PAL_PLATFORM_API_ANDRIOD, - PAL_PLATFORM_API_UIKIT, - PAL_PLATFORM_API_HEADLESS -} PalPlatformApiType; - -/** - * @struct PalPlatformInfo - * @brief Information about a platform (OS). - * - * @since 1.0 - * @ingroup pal_system - */ -typedef struct { - PalPlatformType type; - PalPlatformApiType apiType; - Uint32 totalMemory; /**< Total Disk space (memory) in GB.*/ - Uint32 totalRAM; /**< Total CPU RAM (memory) in MB.*/ - PalVersion version; - char name[PAL_PLATFORM_NAME_SIZE]; /**< (eg. Windows 11.22000).*/ -} PalPlatformInfo; - -/** - * @struct PalCPUInfo - * @brief Information about a CPU. - * - * @since 1.0 - * @ingroup pal_system - */ -typedef struct { - Uint32 numCores; - Uint32 cacheL1; /**< L1 cache in KB.*/ - Uint32 cacheL2; /**< L2 cache in KB.*/ - Uint32 cacheL3; /**< L3 cache in KB.*/ - Uint32 numLogicalProcessors; /**< Number of CPUs.*/ - PalCpuArch architecture; - PalCpuFeatures features; - char vendor[PAL_CPU_VENDOR_NAME_SIZE]; /**< CPU vendor name.*/ - char model[PAL_CPU_MODEL_NAME_SIZE]; /**< CPU modal name.*/ -} PalCPUInfo; - -/** - * @brief Get platform (OS) information. - * - * @param[out] info Pointer to a PalPlatformInfo to receive the platform info. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function is thread-safe if `info` is thread local. - * - * @since 1.0 - * @ingroup pal_system - */ -PAL_API PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info); - -/** - * @brief Get CPU information. - * - * @param[in] allocator Optional user provided allocator. Set to nullptr to - * use default. - * @param[out] info Pointer to a PalCPUInfo to receive the CPU info. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function is thread-safe if the proivded allocator is - * thread safe and `info` is thread local. The default allocator is thread safe. - * - * @since 1.0 - * @ingroup pal_system - */ -PAL_API PalResult PAL_CALL palGetCPUInfo( - const PalAllocator* allocator, - PalCPUInfo* info); - -/** @} */ // end of pal_system group - -#endif // _PAL_SYSTEM_H \ No newline at end of file diff --git a/include/pal/pal_video.h b/include/pal/pal_video.h deleted file mode 100644 index d06c968e..00000000 --- a/include/pal/pal_video.h +++ /dev/null @@ -1,2028 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -/** - * @defgroup pal_video Video - * Video PAL functionality such as windows, cursors, monitors and icons. - * - * @{ - */ - -#ifndef _PAL_VIDEO_H -#define _PAL_VIDEO_H - -#include "pal_event.h" - -/** - * @struct PalMonitor - * @brief Opaque handle to a monitor. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct PalMonitor PalMonitor; - -/** - * @struct PalWindow - * @brief Opaque handle to a window. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct PalWindow PalWindow; - -/** - * @struct PalIcon - * @brief Opaque handle to an icon. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct PalIcon PalIcon; - -/** - * @struct PalCursor - * @brief Opaque handle to a cursor. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct PalCursor PalCursor; - -/** - * @enum PalVideoFeatures - * @brief Video system features. - * - * All video features follow the format `PAL_VIDEO_FEATURE_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_VIDEO_FEATURE_HIGH_DPI = PAL_BIT(0), - PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION = PAL_BIT(1), - PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION = PAL_BIT(2), - PAL_VIDEO_FEATURE_BORDERLESS_WINDOW = PAL_BIT(3), - PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW = PAL_BIT(4), - PAL_VIDEO_FEATURE_TOOL_WINDOW = PAL_BIT(5), - PAL_VIDEO_FEATURE_MONITOR_SET_MODE = PAL_BIT(6), - PAL_VIDEO_FEATURE_MONITOR_GET_MODE = PAL_BIT(7), - PAL_VIDEO_FEATURE_MULTI_MONITORS = PAL_BIT(8), - PAL_VIDEO_FEATURE_WINDOW_SET_SIZE = PAL_BIT(9), - PAL_VIDEO_FEATURE_WINDOW_GET_SIZE = PAL_BIT(10), - PAL_VIDEO_FEATURE_WINDOW_SET_POS = PAL_BIT(11), - PAL_VIDEO_FEATURE_WINDOW_GET_POS = PAL_BIT(12), - PAL_VIDEO_FEATURE_WINDOW_SET_STATE = PAL_BIT(13), - PAL_VIDEO_FEATURE_WINDOW_GET_STATE = PAL_BIT(14), - PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY = PAL_BIT(15), - PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY = PAL_BIT(16), - PAL_VIDEO_FEATURE_WINDOW_SET_TITLE = PAL_BIT(17), - PAL_VIDEO_FEATURE_WINDOW_GET_TITLE = PAL_BIT(18), - PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX = PAL_BIT(19), - PAL_VIDEO_FEATURE_NO_MINIMIZEBOX = PAL_BIT(20), - PAL_VIDEO_FEATURE_CLIP_CURSOR = PAL_BIT(21), - PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION = PAL_BIT(22), - PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY = PAL_BIT(23), - PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL = PAL_BIT(24), - PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS = PAL_BIT(25), - PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS = PAL_BIT(26), - PAL_VIDEO_FEATURE_WINDOW_SET_STYLE = PAL_BIT(27), - PAL_VIDEO_FEATURE_WINDOW_GET_STYLE = PAL_BIT(28), - PAL_VIDEO_FEATURE_CURSOR_SET_POS = PAL_BIT(29), - PAL_VIDEO_FEATURE_CURSOR_GET_POS = PAL_BIT(30), - PAL_VIDEO_FEATURE_WINDOW_SET_ICON = PAL_BIT(31) -} PalVideoFeatures; - -/** - * @enum PalVideoFeatures64 - * @brief Extended Video system features. - * - * All extended video features follow the format `PAL_VIDEO_FEATURE64_**` for - * consistency and API use. - * - * @since 1.3 - * @ingroup pal_video - */ -typedef enum { - PAL_VIDEO_FEATURE64_HIGH_DPI = PAL_BIT64(0), - PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION = PAL_BIT64(1), - PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION = PAL_BIT64(2), - PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW = PAL_BIT64(3), - PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW = PAL_BIT64(4), - PAL_VIDEO_FEATURE64_TOOL_WINDOW = PAL_BIT64(5), - PAL_VIDEO_FEATURE64_MONITOR_SET_MODE = PAL_BIT64(6), - PAL_VIDEO_FEATURE64_MONITOR_GET_MODE = PAL_BIT64(7), - PAL_VIDEO_FEATURE64_MULTI_MONITORS = PAL_BIT64(8), - PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE = PAL_BIT64(9), - PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE = PAL_BIT64(10), - PAL_VIDEO_FEATURE64_WINDOW_SET_POS = PAL_BIT64(11), - PAL_VIDEO_FEATURE64_WINDOW_GET_POS = PAL_BIT64(12), - PAL_VIDEO_FEATURE64_WINDOW_SET_STATE = PAL_BIT64(13), - PAL_VIDEO_FEATURE64_WINDOW_GET_STATE = PAL_BIT64(14), - PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY = PAL_BIT64(15), - PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY = PAL_BIT64(16), - PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE = PAL_BIT64(17), - PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE = PAL_BIT64(18), - PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX = PAL_BIT64(19), - PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX = PAL_BIT64(20), - PAL_VIDEO_FEATURE64_CLIP_CURSOR = PAL_BIT64(21), - PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION = PAL_BIT64(22), - PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY = PAL_BIT64(23), - PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL = PAL_BIT64(24), - PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS = PAL_BIT64(25), - PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS = PAL_BIT64(26), - PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE = PAL_BIT64(27), - PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE = PAL_BIT64(28), - PAL_VIDEO_FEATURE64_CURSOR_SET_POS = PAL_BIT64(29), - PAL_VIDEO_FEATURE64_CURSOR_GET_POS = PAL_BIT64(30), - PAL_VIDEO_FEATURE64_WINDOW_SET_ICON = PAL_BIT64(31), - PAL_VIDEO_FEATURE64_TOPMOST_WINDOW = PAL_BIT64(32), - PAL_VIDEO_FEATURE64_DECORATED_WINDOW = PAL_BIT64(33), - PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY = PAL_BIT64(34), - PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR = PAL_BIT64(35), - PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY = PAL_BIT64(36), - PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS = PAL_BIT64(37), - PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE = PAL_BIT64(38), - PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR = PAL_BIT64(39) -} PalVideoFeatures64; - -/** - * @enum PalOrientation - * @brief Orientation types for a monitor. - * - * All orientation types follow the format `PAL_ORIENTATION_**` for consistency - * and API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_ORIENTATION_LANDSCAPE, - PAL_ORIENTATION_PORTRAIT, - PAL_ORIENTATION_LANDSCAPE_FLIPPED, - PAL_ORIENTATION_PORTRAIT_FLIPPED -} PalOrientation; - -/** - * @enum PalWindowStyle - * @brief Window styles. Multiple styles can be OR'ed together using bitwise - * OR operator (`|`). - * - * All window flags follow the format `PAL_WINDOW_STYLE_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_WINDOW_STYLE_RESIZABLE = PAL_BIT(0), - PAL_WINDOW_STYLE_TRANSPARENT = PAL_BIT(1), - PAL_WINDOW_STYLE_TOPMOST = PAL_BIT(2), - PAL_WINDOW_STYLE_NO_MINIMIZEBOX = PAL_BIT(3), - PAL_WINDOW_STYLE_NO_MAXIMIZEBOX = PAL_BIT(4), - PAL_WINDOW_STYLE_TOOL = PAL_BIT(5), - PAL_WINDOW_STYLE_BORDERLESS = PAL_BIT(6) -} PalWindowStyle; - -/** - * @enum PalWindowState - * @brief Represents the current state of a window. - * - * All window states follow the format `PAL_WINDOW_STATE_**` for consistency and - * API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_WINDOW_STATE_MAXIMIZED, - PAL_WINDOW_STATE_MINIMIZED, - PAL_WINDOW_STATE_RESTORED -} PalWindowState; - -/** - * @enum PalFlashFlag - * @brief Flash flags. Multiple flash flags can be OR'ed together using bitwise - * OR operator (`|`). - * - * `PAL_FLASH_STOP` is not a bit and must not be combined with other bits. - * - * All flash flags follow the format `PAL_FLASH_**` for consistency and - * API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_FLASH_STOP = 0, /**< Stop flashing.*/ - PAL_FLASH_CAPTION = PAL_BIT(0), /**< Flash the titlebar of the window.*/ - PAL_FLASH_TRAY = PAL_BIT(1) /**< Flash the icon of the window.*/ -} PalFlashFlag; - -/** - * @enum PalFBConfigBackend - * @brief Represents the backend of a FBConfig. - * - * All FBConfig backends follow the format `PAL_CONFIG_BACKEND**` for - * consistency and API use. - * - * @since 1.1 - * @ingroup pal_video - */ -typedef enum { - PAL_CONFIG_BACKEND_EGL, - PAL_CONFIG_BACKEND_GLX, - PAL_CONFIG_BACKEND_WGL, - PAL_CONFIG_BACKEND_PAL_OPENGL, /**< Use PAL opengl module backend.*/ - PAL_CONFIG_BACKEND_GLES -} PalFBConfigBackend; - -/** - * @enum PalScancode - * @brief scancodes (layout independent keys) of a keyboard. - * - * All scancodes follow the format `PAL_SCANCODE_**` for consistency and - * API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_SCANCODE_UNKNOWN = 0, - - PAL_SCANCODE_A, - PAL_SCANCODE_B, - PAL_SCANCODE_C, - PAL_SCANCODE_D, - PAL_SCANCODE_E, - PAL_SCANCODE_F, - PAL_SCANCODE_G, - PAL_SCANCODE_H, - PAL_SCANCODE_I, - PAL_SCANCODE_J, - PAL_SCANCODE_K, - PAL_SCANCODE_L, - PAL_SCANCODE_M, - PAL_SCANCODE_N, - PAL_SCANCODE_O, - PAL_SCANCODE_P, - PAL_SCANCODE_Q, - PAL_SCANCODE_R, - PAL_SCANCODE_S, - PAL_SCANCODE_T, - PAL_SCANCODE_U, - PAL_SCANCODE_V, - PAL_SCANCODE_W, - PAL_SCANCODE_X, - PAL_SCANCODE_Y, - PAL_SCANCODE_Z, - - PAL_SCANCODE_0, - PAL_SCANCODE_1, - PAL_SCANCODE_2, - PAL_SCANCODE_3, - PAL_SCANCODE_4, - PAL_SCANCODE_5, - PAL_SCANCODE_6, - PAL_SCANCODE_7, - PAL_SCANCODE_8, - PAL_SCANCODE_9, - - PAL_SCANCODE_F1, - PAL_SCANCODE_F2, - PAL_SCANCODE_F3, - PAL_SCANCODE_F4, - PAL_SCANCODE_F5, - PAL_SCANCODE_F6, - PAL_SCANCODE_F7, - PAL_SCANCODE_F8, - PAL_SCANCODE_F9, - PAL_SCANCODE_F10, - PAL_SCANCODE_F11, - PAL_SCANCODE_F12, - - PAL_SCANCODE_ESCAPE, - PAL_SCANCODE_ENTER, - PAL_SCANCODE_TAB, - PAL_SCANCODE_BACKSPACE, - PAL_SCANCODE_SPACE, - PAL_SCANCODE_CAPSLOCK, - PAL_SCANCODE_NUMLOCK, - PAL_SCANCODE_SCROLLLOCK, - PAL_SCANCODE_LSHIFT, - PAL_SCANCODE_RSHIFT, - PAL_SCANCODE_LCTRL, - PAL_SCANCODE_RCTRL, - PAL_SCANCODE_LALT, - PAL_SCANCODE_RALT, - - PAL_SCANCODE_LEFT, - PAL_SCANCODE_RIGHT, - PAL_SCANCODE_UP, - PAL_SCANCODE_DOWN, - - PAL_SCANCODE_INSERT, - PAL_SCANCODE_DELETE, - PAL_SCANCODE_HOME, - PAL_SCANCODE_END, - PAL_SCANCODE_PAGEUP, - PAL_SCANCODE_PAGEDOWN, - - PAL_SCANCODE_KP_0, - PAL_SCANCODE_KP_1, - PAL_SCANCODE_KP_2, - PAL_SCANCODE_KP_3, - PAL_SCANCODE_KP_4, - PAL_SCANCODE_KP_5, - PAL_SCANCODE_KP_6, - PAL_SCANCODE_KP_7, - PAL_SCANCODE_KP_8, - PAL_SCANCODE_KP_9, - PAL_SCANCODE_KP_ENTER, - PAL_SCANCODE_KP_ADD, - PAL_SCANCODE_KP_SUBTRACT, - PAL_SCANCODE_KP_MULTIPLY, - PAL_SCANCODE_KP_DIVIDE, - PAL_SCANCODE_KP_DECIMAL, - PAL_SCANCODE_KP_EQUAL, - - PAL_SCANCODE_PRINTSCREEN, - PAL_SCANCODE_PAUSE, - PAL_SCANCODE_MENU, - PAL_SCANCODE_APOSTROPHE, - PAL_SCANCODE_BACKSLASH, - PAL_SCANCODE_COMMA, - PAL_SCANCODE_EQUAL, - PAL_SCANCODE_GRAVEACCENT, - PAL_SCANCODE_SUBTRACT, - PAL_SCANCODE_PERIOD, - PAL_SCANCODE_SEMICOLON, - PAL_SCANCODE_SLASH, - PAL_SCANCODE_LBRACKET, - PAL_SCANCODE_RBRACKET, - PAL_SCANCODE_LSUPER, - PAL_SCANCODE_RSUPER, - - PAL_SCANCODE_MAX -} PalScancode; - -/** - * @enum PalKeycode - * @brief Keycodes (layout aware keys) of a keyboard. - * - * All keycodes follow the format `PAL_KEYCODE_**` for consistency and API - * use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_KEYCODE_UNKNOWN = 0, - - PAL_KEYCODE_A, - PAL_KEYCODE_B, - PAL_KEYCODE_C, - PAL_KEYCODE_D, - PAL_KEYCODE_E, - PAL_KEYCODE_F, - PAL_KEYCODE_G, - PAL_KEYCODE_H, - PAL_KEYCODE_I, - PAL_KEYCODE_J, - PAL_KEYCODE_K, - PAL_KEYCODE_L, - PAL_KEYCODE_M, - PAL_KEYCODE_N, - PAL_KEYCODE_O, - PAL_KEYCODE_P, - PAL_KEYCODE_Q, - PAL_KEYCODE_R, - PAL_KEYCODE_S, - PAL_KEYCODE_T, - PAL_KEYCODE_U, - PAL_KEYCODE_V, - PAL_KEYCODE_W, - PAL_KEYCODE_X, - PAL_KEYCODE_Y, - PAL_KEYCODE_Z, - - PAL_KEYCODE_0, - PAL_KEYCODE_1, - PAL_KEYCODE_2, - PAL_KEYCODE_3, - PAL_KEYCODE_4, - PAL_KEYCODE_5, - PAL_KEYCODE_6, - PAL_KEYCODE_7, - PAL_KEYCODE_8, - PAL_KEYCODE_9, - - PAL_KEYCODE_F1, - PAL_KEYCODE_F2, - PAL_KEYCODE_F3, - PAL_KEYCODE_F4, - PAL_KEYCODE_F5, - PAL_KEYCODE_F6, - PAL_KEYCODE_F7, - PAL_KEYCODE_F8, - PAL_KEYCODE_F9, - PAL_KEYCODE_F10, - PAL_KEYCODE_F11, - PAL_KEYCODE_F12, - - PAL_KEYCODE_ESCAPE, - PAL_KEYCODE_ENTER, - PAL_KEYCODE_TAB, - PAL_KEYCODE_BACKSPACE, - PAL_KEYCODE_SPACE, - PAL_KEYCODE_CAPSLOCK, - PAL_KEYCODE_NUMLOCK, - PAL_KEYCODE_SCROLLLOCK, - PAL_KEYCODE_LSHIFT, - PAL_KEYCODE_RSHIFT, - PAL_KEYCODE_LCTRL, - PAL_KEYCODE_RCTRL, - PAL_KEYCODE_LALT, - PAL_KEYCODE_RALT, - - PAL_KEYCODE_LEFT, - PAL_KEYCODE_RIGHT, - PAL_KEYCODE_UP, - PAL_KEYCODE_DOWN, - - PAL_KEYCODE_INSERT, - PAL_KEYCODE_DELETE, - PAL_KEYCODE_HOME, - PAL_KEYCODE_END, - PAL_KEYCODE_PAGEUP, - PAL_KEYCODE_PAGEDOWN, - - PAL_KEYCODE_KP_0, - PAL_KEYCODE_KP_1, - PAL_KEYCODE_KP_2, - PAL_KEYCODE_KP_3, - PAL_KEYCODE_KP_4, - PAL_KEYCODE_KP_5, - PAL_KEYCODE_KP_6, - PAL_KEYCODE_KP_7, - PAL_KEYCODE_KP_8, - PAL_KEYCODE_KP_9, - PAL_KEYCODE_KP_ENTER, - PAL_KEYCODE_KP_ADD, - PAL_KEYCODE_KP_SUBTRACT, - PAL_KEYCODE_KP_MULTIPLY, - PAL_KEYCODE_KP_DIVIDE, - PAL_KEYCODE_KP_DECIMAL, - PAL_KEYCODE_KP_EQUAL, - - PAL_KEYCODE_PRINTSCREEN, - PAL_KEYCODE_PAUSE, - PAL_KEYCODE_MENU, - PAL_KEYCODE_APOSTROPHE, - PAL_KEYCODE_BACKSLASH, - PAL_KEYCODE_COMMA, - PAL_KEYCODE_EQUAL, - PAL_KEYCODE_GRAVEACCENT, - PAL_KEYCODE_SUBTRACT, - PAL_KEYCODE_PERIOD, - PAL_KEYCODE_SEMICOLON, - PAL_KEYCODE_SLASH, - PAL_KEYCODE_LBRACKET, - PAL_KEYCODE_RBRACKET, - PAL_KEYCODE_LSUPER, - PAL_KEYCODE_RSUPER, - - PAL_KEYCODE_MAX -} PalKeycode; - -/** - * @enum PalMouseButton - * @brief Buttons of a mouse. - * - * All mouse buttons follow the format `PAL_MOUSE_BUTTON_**` for - * consistency and API use. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef enum { - PAL_MOUSE_BUTTON_UNKNOWN = 0, - - PAL_MOUSE_BUTTON_LEFT, - PAL_MOUSE_BUTTON_RIGHT, - PAL_MOUSE_BUTTON_MIDDLE, - PAL_MOUSE_BUTTON_X1, - PAL_MOUSE_BUTTON_X2, - - PAL_MOUSE_BUTTON_MAX -} PalMouseButton; - -/** - * @enum PalCursorType - * @brief System cursor types. - * - * All cursor types follow the format `PAL_CURSOR_**` for - * consistency and API use. - * - * @since 1.1 - * @ingroup pal_video - */ -typedef enum { - PAL_CURSOR_ARROW, - PAL_CURSOR_HAND, - PAL_CURSOR_CROSS, - PAL_CURSOR_IBEAM, - PAL_CURSOR_WAIT, - - PAL_CURSOR_MAX -} PalCursorType; - -/** - * @struct PalMonitorInfo - * @brief Information about a monitor. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - bool primary; /**< True if this is the primary monitor.*/ - Uint32 dpi; - Uint32 refreshRate; - Int32 x; /**< X position in pixels.*/ - Int32 y; /**< Y position in pixels.*/ - Uint32 width; /**< Width in pixels.*/ - Uint32 height; /**< Height in pixels.*/ - PalOrientation orientation; - char name[32]; -} PalMonitorInfo; - -/** - * @struct PalMonitorMode - * @brief information about a monitor display mode. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - Uint32 bpp; /**< Bits per pixel.*/ - Uint32 refreshRate; - Uint32 width; /**< Width in pixels.*/ - Uint32 height; /**< Height in pixels.*/ -} PalMonitorMode; - -/** - * @struct PalFlashInfo - * @brief Parameters for flashing a window to request focus. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - Uint32 interval; /**< In milliseconds. Set to 0 for default.*/ - PalFlashFlag flags; /**< See PalFlashFlag.*/ - Uint32 count; /**< Set to 0 to flash until focused or cancelled.*/ -} PalFlashInfo; - -/** - * @struct PalIconCreateInfo - * @brief Creation parameters for an icon. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - Uint32 width; /**< Width in pixels.*/ - Uint32 height; /**< Height in pixels.*/ - const Uint8* pixels; /**< Pixels in `RGBA` format.*/ -} PalIconCreateInfo; - -/** - * @struct PalCursorCreateInfo - * @brief Creation parameters for a cursor. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - Uint32 width; /**< Width in pixels..*/ - Uint32 height; /**< Height in pixels.*/ - Int32 xHotspot; /**< X pixel for detecting clicks.*/ - Int32 yHotspot; /**< Y pixel for detecting clicks.*/ - const Uint8* pixels; /**< Pixels in `RGBA` format.*/ -} PalCursorCreateInfo; - -/** - * @struct PalWindowHandleInfo - * @brief Information about a window handle. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - void* nativeDisplay; /**< The platform (OS) display.*/ - void* nativeWindow; /**< The window platform (OS) handle.*/ -} PalWindowHandleInfo; - -/** - * @struct PalWindowHandleInfoEx - * @brief Extended information about a window handle. - * - * @since 1.3 - * @ingroup pal_video - */ -typedef struct { - void* nativeDisplay; /**< The platform (OS) display.*/ - void* nativeWindow; /**< The window platform (OS) handle.*/ - void* nativeHandle1; /**< Extra window handle (xdgSurface)*/ - void* nativeHandle2; /**< Extra window handle (xdgToplevel)*/ - void* nativeHandle3; /**< Extra window handle (wl_egl_window)*/ -} PalWindowHandleInfoEx; - -/** - * @struct PalWindowCreateInfo - * @brief Creation parameters for a window. - * - * Uninitialized fields may result in undefined behavior. - * - * @since 1.0 - * @ingroup pal_video - */ -typedef struct { - bool show; /**< Show after creation.*/ - bool maximized; /**< Maximize after creation.*/ - bool minimized; /**< Minimze after creation.*/ - bool center; /**< Center after creation.*/ - Uint32 width; /**< Width in pixels.*/ - Uint32 height; /**< Width in pixels.*/ - PalWindowStyle style; /**< Window style.*/ - const char* title; /**< Title in UTF-8 encoding.*/ - PalMonitor* monitor; /**< Set to nullptr to use primary monitor.*/ -} PalWindowCreateInfo; - -/** - * @brief Initialize the video system. - * - * This must be called before any video function. The video system must be - * shutdown with palShutdownVideo() when no longer needed. - * - * The allocator will not not copied, therefore the pointer must remain valid - * until the video system is shutdown. The event driver must be valid to recieve - * video events. - * - * @param[in] allocator Optional user-provided allocator. Set to nullptr to use - * default. - * @param[in] eventDriver Optional user-provided event driver. This is needed to - * push video events. Set to nullptr to use default. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palShutdownVideo - * @sa palSetPreferredInstance - */ -PAL_API PalResult PAL_CALL palInitVideo( - const PalAllocator* allocator, - PalEventDriver* eventDriver); - -/** - * @brief Shutdown the video system. - * - * If the video system has not been initialized, the function returns silently. - * All created windows, icons, and cursors must be destroyed before this call. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palInitVideo - */ -PAL_API void PAL_CALL palShutdownVideo(); - -/** - * @brief Update the video system and all created windows. - * - * If the video system has not been initialized, the function returns silently. - * This function pushes generated video events to the event driver set at - * palInitVideo(). - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palInitVideo - */ -PAL_API void PAL_CALL palUpdateVideo(); - -/** - * @brief Get the supported features of the video system. - * - * The video system must be initialized before this call. - * - * @return video features on success or `0` on failure. - * - * Thread safety: This function is thread safe. - * - * @since 1.0 - * @ingroup pal_video - * @sa palInitVideo - */ -PAL_API PalVideoFeatures PAL_CALL palGetVideoFeatures(); - -/** - * @brief Get the supported features of the video system. - * - * The video system must be initialized before this call. - * This returns the supported features from palGetVideoFeatures() - * and adds additionally supported features. - * - * @return video features on success or `0` on failure. - * - * Thread safety: This function is thread safe. - * - * @since 1.3 - * @ingroup pal_video - * @sa palInitVideo - */ -PAL_API PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx(); - -/** - * @brief Set the FBConfig for the video system. - * - * The video system must be initialized before this call. - * The provided FBConfig will be used for all created windows after this call. - * The `index` is the loop index from the drivers - * supported FBConfigs. - * - * The `backend` is used to tell the video system, the source of the index. - * Examples: PAL_CONFIG_BACKEND_EGL tells the video system, we got this loop - * index from EGL. This will enable the video system to find your FBConfig. - * - * Example Flow: - * Enumerate and select your FBConfig using any backend(EGL, GLX, WGL, etc) - * and just let the video system know which one you used. - * - * If the backend passed is not the same as the one used, - * the video system might still get a FBConfig but it will not be the - * one requested. - * - * @param[in] index The FBConfig driver index. - * @param[in] backend The FBConfig backend or source. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.1 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetFBConfig( - const int index, - PalFBConfigBackend backend); - -/** - * @brief Return a list of all connected monitors. - * - * The video system must be initialized before this call. - * - * Call this function first with PalMonitor array set to nullptr to get the - * number of connected monitors. Allocate memory for the PalMonitor - * array and passed in the count and the allocated array. If the count of the - * array is less than the number of connected monitors, PAL will write upto that - * limit. - * - * If the count is 0 and the PalMonitor array is nullptr, the function fails - * and returns `PAL_RESULT_INSUFFICIENT_BUFFER`. - * - * The monitor handles must not be freed by the user, they are managed by the - * platform (OS). Users are required to cache this, and call this function again - * if monitors are added or removed. - * - * @param[in] count Capacity of the PalMonitor array. - * @param[out] monitors User allocated array of PalMonitor. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetPrimaryMonitor - */ -PAL_API PalResult PAL_CALL palEnumerateMonitors( - Int32* count, - PalMonitor** outMonitors); - -/** - * @brief Get the primary connected monitor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY` must be supported. - * - * The monitor handle must not be freed by the user, they are managed by the - * platform (OS). - * - * @param[out] outMonitor Pointer to a PalMonitor to recieve the primary - * monitor. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palEnumerateMonitors - */ -PAL_API PalResult PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor); - -/** - * @brief Get information about a monitor. - * - * The video system must be initialized before this call. - * - * This function takes in a PalMonitorInfo and fills it. - * Some of the fields are set to defaults if the operation is not supported on - * the platform (OS). example: On Windows 7, DPI will always be 96. - * - * @param[in] monitor Monitor to query information on. - * @param[out] info Pointer to a PalMonitorInfo to fill. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info); - -/** - * @brief Return a list of all supported monitor display modes for the provided - * monitor. - * - * The video system must be initialized before this call. - * - * Call this function first with PalMonitorMode array set to nullptr to get the - * number of supported monitor display modes. Allocate memory for the - * PalMonitorMode array and passed in the count and the allocated array. If the - * count of the array is less than the number of supported monitor display - * modes, PAL will write upto that limit. - * - * If the count is 0 and the PalMonitorMode array is nullptr, the function fails - * and returns `PAL_RESULT_INSUFFICIENT_BUFFER`. - * - * @param[in] monitor Monitor to query display modes on. - * @param[in] count Capacity of the PalMonitorMode array. - * @param[out] modes User allocated array of PalMonitorMode. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes); - -/** - * @brief Get the current monitor display mode of the provided monitor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_MONITOR_GET_MODE` must be supported. - * - * @param[in] monitor Monitor to query its current display mode. - * @param[out] mode Pointer to a PalMonitorMode to recieve the current monitor. - * mode. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palSetMonitorMode - */ -PAL_API PalResult PAL_CALL palGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode); - -/** - * @brief Set the active display monitor mode of the provided monitor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_MONITOR_SET_MODE` Must be supported. - * - * PAL only validates the monitor display mode pointer not the values. To be - * safe, users must get the monitor mode from palEnumerateMonitorModes() or call - * palValidateMonitorMode() to validate before switching. - * palValidateMonitorMode() is not supported on all platforms. - * - * If the monitor display mode submitted is invalid, this function might fail - * depending on the platform (OS). - * - * @param[in] monitor Monitor to set its current display mode. - * @param[in] mode Pointer to a PalMonitorMode to set. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetCurrentMonitorMode - */ -PAL_API PalResult PAL_CALL palSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode); - -/** - * @brief Check if a monitor display mode is valid on the provided monitor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE` must be supported. - * - * @param[in] monitor The monitor. - * @param[in] mode Pointer to a PalMonitorMode to validate. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode); - -/** - * @brief Set the orientation for a monitor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION` must be supported. - * - * This change is temporary and is reset when the platform (OS) reboots. - * - * @param[in] monitor Monitor to set its orientation. - * @param[in] orientation The orientation to set. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation); - -/** - * @brief Create a window. - * - * The video system must be initialized before this call. - * - * @param[in] info Pointer to a PalWindowCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outWindow Pointer to a PalWindow to recieve the created - * window. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @note On Wayland - * - * - creating non resizable windows is not supported. - * PAL will always creating resizable windows. - * - * - Creating windows on a specific monitor is not supported. - * - * - Creating hidden window is not supported. It will be ignored. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow); - -/** - * @brief Destroy the provided window. - * - * The video system must be initialized before this call. - * If the provided window is invalid or nullptr, this function returns - * silently. This only destroys windows created by PAL. - * - * @param[in] window Pointer to the window to destroy. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palCreateWindow - */ -PAL_API void PAL_CALL palDestroyWindow(PalWindow* window); - -/** - * @brief Minimize a maximized or restored window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported. - * If the window is already minimized, this functions does nothing. - * - * @param[in] window Window to minimize. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palMaximizeWindow - * @sa palRestoreWindow - */ -PAL_API PalResult PAL_CALL palMinimizeWindow(PalWindow* window); - -/** - * @brief Maximize a minimized or restored window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported. - * If the window is already maximized, this functions does nothing. - * - * @param[in] window Window to maximize. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palMinimizeWindow - * @sa palRestoreWindow - */ -PAL_API PalResult PAL_CALL palMaximizeWindow(PalWindow* window); - -/** - * @brief Restores a window to it previous state. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported. - * If the window is already restored, this functions does nothing. - * - * @param[in] window Window to restore. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @note Wayland does not support restoring a minimized windows. - * - * @since 1.0 - * @ingroup pal_video - * @sa palMinimizeWindow - * @sa palMaximizeWindow - */ -PAL_API PalResult PAL_CALL palRestoreWindow(PalWindow* window); - -/** - * @brief Show the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY` must be supported. - * All windows are created hidden if not explicitly shown. - * This does nothing if the window is already shown. - * - * @param[in] window Window to show. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palHideWindow - */ -PAL_API PalResult PAL_CALL palShowWindow(PalWindow* window); - -/** - * @brief Hide the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY` must be supported. - * This does nothing if the window is already hidden. - * - * @param[in] window Window to hide. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palShowWindow - */ -PAL_API PalResult PAL_CALL palHideWindow(PalWindow* window); - -/** - * @brief Request the platform (OS) to visually flash the provided window. - * - * The video system must be initialized before this call. - * - * If `PAL_FLASH_CAPTION` is used, `PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION` must - * be supported. - * - * If `PAL_FLASH_TRAY` is used, `PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY` must be - * supported. - * - * @param[in] window Pointer to the window. - * @param[in] info Pointer to a PalFlashInfo struct with flash paramters. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palFlashWindow( - PalWindow* window, - const PalFlashInfo* info); - -/** - * @brief Get the style of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_STYLE` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] outStyle Pointer to a PalWindowStyle to recieve the window style. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palSetWindowStyle - */ -PAL_API PalResult PAL_CALL palGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle); - -/** - * @brief Get the monitor the provided window is currently on. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] outMonitor Pointer to a PalMonitor to recieve the monitor. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palGetWindowMonitor( - PalWindow* window, - PalMonitor** outMonitor); - -/** - * @brief Get the title of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_TITLE` must be supported. - * - * Set the buffer to nullptr to get the size of the window name in bytes. - * If the size of the provided buffer is less than the actual size of window - * title, PAL will write upto that limit. - * - * @param[in] window The window to query its title. - * @param[in] bufferSize Size of the provided buffer in bytes. - * @param[out] outSize The actual size of the window title in bytes. - * @param[out] outBuffer Pointer to a user provided buffer to recieve the title. - * Can be nullptr. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palSetWindowTitle - */ -PAL_API PalResult PAL_CALL palGetWindowTitle( - PalWindow* window, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer); - -/** - * @brief Get the position of the provided window in pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_POS` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] x Pointer to recieve the window x position. Can be nullptr. - * @param[out] y Pointer to recieve the window y position. Can be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palSetWindowPos - */ -PAL_API PalResult PAL_CALL palGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y); - -/** - * @brief Get the size of the provided window in pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_SIZE` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] width Pointer to recieve the width. Can be nullptr. - * @param[out] height Pointer to recieve the height. Can be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palSetWindowSize - */ -PAL_API PalResult PAL_CALL palGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height); - -/** - * @brief Get the state of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_STATE` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] outState Pointer to a PalWindowState to recieve the window state. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palGetWindowState( - PalWindow* window, - PalWindowState* outState); - -/** - * @brief Get the state of the keycodes (layout aware keys) of the - * keyboard. - * - * The video system must be initialized before this call. - * - * The returned pointer must not be freed. The state is updated when - * palUpdateVideo() is called. The array must be index with PalKeycodes and - * not exceed `PAL_KEYCODE_MAX`. - * - * @return A pointer to the keycodes array on success or nullptr on failure. - * - * Thread safety: This function is thread-safe. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API const bool* PAL_CALL palGetKeycodeState(); - -/** - * @brief Get the state of the scancodes (layout independent keys) of - * the keyboard. - * - * The video system must be initialized before this call. - * - * The returned pointer must not be freed. The state is updated when - * palUpdateVideo() is called. The array must be index with PalScancodes and - * not exceed PAL_SCANCODE_MAX. - * - * @return A pointer to the scancodes array on success or nullptr on failure. - * - * Thread safety: This function is thread-safe. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API const bool* PAL_CALL palGetScancodeState(); - -/** - * @brief Get the state of the buttons of the mouse. - * - * The video system must be initialized before this call. - * - * The returned pointer must not be freed. The state is updated when - * palUpdateVideo() is called. The array must be index with PalMouseButton and - * not exceed `PAL_MOUSE_BUTTON_MAX`. - * - * @return A pointer to the mouse button array on success or nullptr on failure. - * - * @Thread safety: This function is thread-safe. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API const bool* PAL_CALL palGetMouseState(); - -/** - * @brief Get the relative movement of the mouse in desktop pixels. - * - * The video system must be initialized before this call. - * The relative movement will be updated when palUpdateVideo() is called. - * - * @param[in] dx Pointer to recieve the mouse relative movement x. Can be - * nullptr. - * @param[in] dy Pointer to recieve the mouse relative movement y. Can be - * nullptr. - * - * Thread safety: This function is thread-safe if `dx` and `dy` are thread - * local. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API void PAL_CALL palGetMouseDelta( - Int32* dx, - Int32* dy); - -/** - * @brief Get the wheel delta of the mouse. - * - * The video system must be initialized before this call. - * The wheel delta will be updated when palUpdateVideo() is called. - * - * @param[in] dx Pointer to recieve the mouse wheel delta x. Can be nullptr. - * @param[in] dy Pointer to recieve the mouse wheel delta y. Can be nullptr. - * - * Thread safety: This function is thread-safe if `dx` and `dy` are thread - * local. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API void PAL_CALL palGetMouseWheelDelta( - Int32* dx, - Int32* dy); - -/** - * @brief Get the raw wheel delta of the mouse in floats. - * - * The video system must be initialized before this call. - * The wheel delta will be updated when palUpdateVideo() is called. - * - * @param[in] dx Pointer to recieve the mouse wheel delta x in floats. Can be - * nullptr. - * @param[in] dy Pointer to recieve the mouse wheel delta y in floats. Can be - * nullptr. - * - * Thread safety: This function is thread-safe if `dx` and `dy` are thread - * local. - * - * @since 1.3 - * @ingroup pal_video - */ -PAL_API void PAL_CALL palGetRawMouseWheelDelta( - float* dx, - float* dy); - -/** - * @brief Check if the provided window is visible. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY` must be supported. - * - * @param[in] window Pointer to the window. - * - * @return `true` if the window is visible otherwise `false`. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API bool PAL_CALL palIsWindowVisible(PalWindow* window); - -/** - * @brief Get the current input-focused window per application. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS` must be supported. - * - * @return The current input-focused window on success or nullptr on - * failure. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalWindow* PAL_CALL palGetFocusWindow(); - -/** - * @brief Get the native handle of the provided window. - * - * The video system must be initialized before this call. - * - * @param[in] window Pointer to the window. - * - * @return The native handle of the window on success or nullptr on failure. - * - * Thread safety: This function is thread-safe. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window); - -/** - * @brief Get the native handles of the provided window. - * - * The video system must be initialized before this call. - * - * On Wayland: `PalWindowHandleInfoEx::nativeHandle1`, - * `PalWindowHandleInfoEx::nativeHandle2` and - * `PalWindowHandleInfoEx::nativeHandle3` are xdg_surface, xdg_toplevel - * and wl_egl_window respectively. - * - * @param[in] window Pointer to the window. - * - * @return The native handles of the window on success or nullptr on failure. - * - * Thread safety: This function is thread-safe. - * - * @since 1.3 - * @ingroup pal_video - */ -PAL_API PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* w); - -/** - * @brief Set the opacity of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW` must be supported. - * The window must have `PAL_WINDOW_STYLE_TRANSPARENT` style. - * - * @param[in] window Pointer to the window. - * @param[in] opacity Must be in the range 0.0 - 1.0. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetWindowOpacity( - PalWindow* window, - float opacity); - -/** - * @brief Set the style of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_STYLE` must be supported. - * - * @param[in] window Pointer to the window. - * @param[in] style The style to set. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetWindowStyle - */ -PAL_API PalResult PAL_CALL palSetWindowStyle( - PalWindow* window, - PalWindowStyle style); - -/** - * @brief Set the title of the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_TITLE` must be supported. - * The title must be a UTF-8 encoding null terminated string. - * - * @param[in] window Pointer to the window. - * @param[in] title UTF-8 encoding null terminated string. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetWindowTitle - */ -PAL_API PalResult PAL_CALL palSetWindowTitle( - PalWindow* window, - const char* title); - -/** - * @brief Set the position of the provided window in pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_POS` must be supported. - * - * @param[in] window Pointer to the window. - * @param[in] x The new x coordinate in pixels. - * @param[in] y The new y coordinate in pixels. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetWindowPos - */ -PAL_API PalResult PAL_CALL palSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y); - -/** - * @brief Set the size of the provided window in pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_SIZE` must be supported. - * - * @param[in] window Pointer to the window. - * @param[in] width The new width of the window in pixels. Must be greater than - * zero. - * @param[in] height The new height of the window in pixels. Must be greater - * than zero. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetWindowSize - */ -PAL_API PalResult PAL_CALL palSetWindowSize( - PalWindow* window, - Uint32 width, - Uint32 height); - -/** - * @brief Request input focus for the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS` must be supported. - * - * @param[in] window Pointer to the window. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palGetFocusWindow - */ -PAL_API PalResult PAL_CALL palSetFocusWindow(PalWindow* window); - -/** - * @brief Create an icon. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported. - * - * @param[in] info Pointer to a PalIconCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outIcon Pointer to a PalIcon to recieve the created - * icon. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palDestroyIcon - */ -PAL_API PalResult PAL_CALL palCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon); - -/** - * @brief Destroy the provided icon. - * - * The video system must be initialized before this call. - * - * If the provided icon is invalid or nullptr, this function returns - * silently. - * - * @param[in] icon Pointer to the icon to destroy. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palCreateIcon - */ -PAL_API void PAL_CALL palDestroyIcon(PalIcon* icon); - -/** - * @brief Set the icon for the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported. - * - * @param[in] window Pointer to the window. - * @param[in] icon Pointer to the icon. Set to nullptr to revert. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetWindowIcon( - PalWindow* window, - PalIcon* icon); - -/** - * @brief Create a cursor. - * - * The video system must be initialized before this call. - * - * @param[in] info Pointer to a PalCursorCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outCursor Pointer to a PalCursor to recieve the created - * cursor. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palDestroyCursor - */ -PAL_API PalResult PAL_CALL palCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor); - -/** - * @brief Create a system cursor. - * - * The video system must be initialized before this call. - * - * @param[in] type The system cursor type to create. Must not be nullptr. - * @param[out] outCursor Pointer to a PalCursor to recieve the created - * cursor. Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must only be called from the main thread. - * - * @since 1.1 - * @ingroup pal_video - * @sa palDestroyCursor - */ -PAL_API PalResult PAL_CALL palCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor); - -/** - * @brief Destroy the provided cursor. - * - * The video system must be initialized before this call. - * - * If the provided icon is invalid or nullptr, this function returns - * silently. - * - * @param[in] cursor Pointer to the cursor to destroy. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - * @sa palCreateCursor - */ -PAL_API void PAL_CALL palDestroyCursor(PalCursor* cursor); - -/** - * @brief Show or hide the cursor. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY` must be supported. - * - * This affects all created cursors since the platform (OS) merges all cursors - * into a single one on the screen. - * - * @param[in] show True to make the cursor visible otherwise false. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API void PAL_CALL palShowCursor(bool show); - -/** - * @brief Clip the cursor to the provided window. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_CLIP_CURSOR` must be supported. - * - * If the window is destroyed without unclipping the cursor, this cursor might - * not reset depending on the platform (OS). To be safe, unclip the cursor from - * the window before destroying the window. - * - * @param[in] window Pointer to the window. - * @param[in] clip True to clip to window or false to unclip. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palClipCursor( - PalWindow* window, - bool clip); - -/** - * @brief Get the position of the cursor relative to the provided window in - * pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_CURSOR_GET_POS` must be supported. - * - * @param[in] window Pointer to the window. - * @param[out] x Pointer to recieve the x position. Can be - * nullptr. - * @param[out] y Pointer to recieve the y position. Can be - * nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palGetCursorPos( - PalWindow* window, - Int32* x, - Int32* y); - -/** - * @brief Set the position of the cursor relative to the provided window in - * pixels. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_CURSOR_SET_POS` must be supported. - * - * @param[in] window Pointer to the window. - * @param[in] x The new x coordinate of the cursor in pixels. - * @param[in] y The new y coordinate of the cursor in pixels. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetCursorPos( - PalWindow* window, - Int32 x, - Int32 y); - -/** - * @brief Set the cursor for the provided window. - * - * The video system must be initialized before this call. - * - * @param[in] window Pointer to the window. - * @param[in] cursor Pointer to the cursor. Set to nullptr to revert. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.0 - * @ingroup pal_video - */ -PAL_API PalResult PAL_CALL palSetWindowCursor( - PalWindow* window, - PalCursor* cursor); - -/** - * @brief Get the native application instance or display. - * - * The video system must be initialized before this call. - * - * This returns the native instance or display of the application - * PAL video was initialized in. - * - * On Linux: This is the Display associated with the connection. - - * On Windows: This is the HINSTANCE of the process. - * - * @return The instance or display on success or nullptr on failure. - * - * Thread safety: This function is thread safe. - * - * @note The returned instance or display must not be freed. - * - * @since 1.2 - * @ingroup pal_video - */ -PAL_API void* PAL_CALL palGetInstance(); - -/** - * @brief Attach a foreign or native window to PAL video system. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported. - * - * This function registers the provided window with PAL video system so it - * can manage events and use its functionality/API for the provided window. - * - * PAL does not own the window, it just sends events to that window. - * Users are responsible for destroying the window when no longer needed. - * palDestroyWindow() does not destroy the foreign or native window. - * - * Use Case: - * - * PAL takes your native foreign or native window and gives you a PalWindow - * which can be used with all of PAL API. The native window must be valid - * till the PalWindow has been detached with palDetachWindow(). - * - * The window must be created with the same instance or display - * that PAL uses. see palGetInstance(). - * - * @param[in] windowHandle Pointer to the foreign or native window. - * @param[out] outWindow Pointer to a PalWindow to recieve the attached window. - * Must not be nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.2 - * @ingroup pal_video - * @sa palGetInstance - * @sa palDestroyWindow - * @sa palDetachWindow - */ -PAL_API PalResult PAL_CALL palAttachWindow( - void* windowHandle, - PalWindow** outWindow); - -/** - * @brief Detach a foreign or native window from PAL video system. - * - * The video system must be initialized before this call. - * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported. - * - * This function unregisters the provided window from PAL video system. - * The window must not be owned by PAL otherwise the function fails - * and return `PAL_RESULT_INVALID_WINDOW`. - * - * Detaching the window does not destroy the window, - * therefore destroying the window is the users responsibility. - * - * Use Case: - * - * Give back the PalWindow returned at palAttachWindow() - * and get back your native window. - * - * @param[in] window Pointer to the PalWindow to detach. Must not be nullptr. - * @param[out] outWindowHandle Pointer to recieve the native window. Can be - * nullptr. - * - * @return `PAL_RESULT_SUCCESS` on success or a result code on - * failure. Call palFormatResult() for more information. - * - * Thread safety: This function must be called from the main thread. - * - * @since 1.2 - * @ingroup pal_video - * @sa palAttachWindow - */ -PAL_API PalResult PAL_CALL palDetachWindow( - PalWindow* window, - void** outWindowHandle); - -/** - * @brief Set the preferred instance the video system should use. - * - * This function must be called before palInitVideo(). This will be ignored - * if the video system is already initialized. - * If there is no preferred instance set, the video system creates one. - * - * On Linux: This is the Display associated with the connection. - - * On Windows: This is the HINSTANCE of the process. - * - * Thread safety: This function must be called from the main thread. - * - * @note The provided instance will not be freed by the video system. - * - * @since 1.3 - * @ingroup pal_video - * @sa palInitVideo - */ -PAL_API void PAL_CALL palSetPreferredInstance(void* instance); - -/** @} */ // end of pal_video group - -#endif // _PAL_VIDEO_H \ No newline at end of file diff --git a/include/pal2/pal_core.h b/include/pal2/pal_core.h new file mode 100644 index 00000000..6543167a --- /dev/null +++ b/include/pal2/pal_core.h @@ -0,0 +1,593 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_core Core + * @ingroup pal_core + * @{ + */ + +#ifndef _PAL_CORE_H +#define _PAL_CORE_H + +#include +#include + +#ifdef __cplusplus +#define PAL_EXTERN_C extern "C" +#else +#define PAL_EXTERN_C +#define nullptr ((void*)0) +#endif // __cplusplus + +// Set up shared library dependencies +#ifdef _WIN32 +#define PAL_CALL __stdcall +#ifdef _PAL_EXPORT +#define PAL_DECLSPEC PAL_EXTERN_C __declspec(dllexport) +#else +#define PAL_DECLSPEC PAL_EXTERN_C __declspec(dllimport) +#endif // PAL_EXPORT +#else +// other plafforms +#define PAL_CALL +#ifdef _PAL_EXPORT +#define PAL_DECLSPEC PAL_EXTERN_C __attribute__((visibility("default"))) +#else +#define PAL_DECLSPEC PAL_EXTERN_C +#endif // PAL_EXPORT +#endif // _WIN32 + +#ifdef _PAL_BUILD_DLL +#define PAL_API PAL_EXTERN_C PAL_DECLSPEC +#else +// static library +#define PAL_API PAL_EXTERN_C +#endif // _PAL_BUILD_DLL + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define PAL_BIG_ENDIAN 1 +#else +#define PAL_BIG_ENDIAN 0 +#endif // __ORDER_BIG_ENDIAN__ + +#define PAL_TRUE 1 +#define PAL_FALSE 0 +#define PAL_INFINITE UINT32_MAX +#define PAL_RESULT_SUCCESS 0 + +#define PAL_RESULT_CODE_INVALID_ARGUMENT 1 +#define PAL_RESULT_CODE_OUT_OF_MEMORY 2 +#define PAL_RESULT_CODE_PLATFORM_FAILURE 3 +#define PAL_RESULT_CODE_TIMEOUT 4 +#define PAL_RESULT_CODE_INVALID_HANDLE 5 +#define PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED 6 +#define PAL_RESULT_CODE_INVALID_OPERATION 7 +#define PAL_RESULT_CODE_DEVICE_LOST 8 +#define PAL_RESULT_CODE_OUT_OF_DATE 9 +#define PAL_RESULT_CODE_COUNT 10 + +#define PAL_RESULT_SOURCE_NONE 0 +#define PAL_RESULT_SOURCE_WIN32 1 +#define PAL_RESULT_SOURCE_POSIX 2 +#define PAL_RESULT_SOURCE_EGL 3 +#define PAL_RESULT_SOURCE_VULKAN 4 +#define PAL_RESULT_SOURCE_D3D12 5 +#define PAL_RESULT_SOURCE_METAL 6 +#define PAL_RESULT_SOURCE_COUNT 7 + +/** + * @typedef PalBool + * @brief Must be `PAL_TRUE` or `PAL_FALSE`. + */ +typedef uint32_t PalBool; + +/** + * @typedef PalResult + * @brief Value returned by most PAL functions. + * + * `PalResult` constains the PAL result code (eg. `PAL_RESULT_CODE_INVALID_HANDLE`), the native + * source (eg. `PAL_RESULT_SOURCE_POSIX`) and the native code itself. + * The native code and the source are optional and both can be zero if not provided. + * + * Only `PAL_RESULT_SUCCESS` is guarantee to be checked directly with the result value. + * To check specific result codes for fast path error handling, + * Call `palGetResultCode(result)` to get the PAL result code from the result value. + * + * Call `palGetResultSource(result)` and `palGetResultNativeCode(result)` to get the native + * source and native code. The native source shows where the native code was retrieved from. + * Example: `PAL_RESULT_SOURCE_WIN32` means the native code was retrieved from win32 + * (`GetLastError()`). + * + * @since 2.0 + */ +typedef uint64_t PalResult; + +/** + * @typedef PalResultCode + * @brief Result codes that are extracted from `PalResult`. + * + * `palGetResultCode(result)` to get the result code from a result value. + * + * All result codes follow the format `PAL_RESULT_CODE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint16_t PalResultCode; + +/** + * @typedef PalResultSource + * @brief Result sources that are extracted from `PalResult`. + * + * `palGetResultSource(result)` to get the result source from a result value. + * + * All result sources follow the format `PAL_RESULT_SOURCE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint16_t PalResultSource; + +/** + * @typedef PalAllocateFn + * @brief Function pointer type used for memory allocations. + * + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * @param[in] size Number of bytes to allocate. + * @param[in] alignment Must be power of two. Set to 0 to use implementation-defined default. + * + * @return Pointer to the allocated memory on success or `nullptr` on failure. + * + * @since 2.0 + * @sa PalFreeFn + */ +typedef void*(PAL_CALL* PalAllocateFn)( + void* userData, + uint64_t size, + uint64_t alignment); + +/** + * @typedef PalFreeFn + * @brief Function pointer type used for memory deallocations. + * + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * @param[in] ptr Pointer to memory previously allocated by PalAllocateFn. + * + * @since 2.0 + * @sa PalAllocateFn + */ +typedef void(PAL_CALL* PalFreeFn)( + void* userData, + void* ptr); + +/** + * @typedef PalLogCallback + * @brief Function pointer type used for log callbacks. + * + * @param userData Optional pointer to user data passed from ::PalLogger. Can be `nullptr`. + * @param msg Null-terminated UTF-8 log message. + * + * @since 2.0 + * @sa palLog + */ +typedef void(PAL_CALL* PalLogCallback)( + void* userData, + const char* msg); + +/** + * @struct PalVersion + * @brief Describes the version of PAL. + * + * @since 2.0 + */ +typedef struct { + uint32_t major; /**< Major version (breaking changes).*/ + uint32_t minor; /**< Minor version (adding features).*/ + uint32_t build; /**< Build version (bug fixes).*/ +} PalVersion; + +/** + * @struct PalAllocator + * @brief Custom memory allocator. + * + * Provides user-defined memory allocation and free functions. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalAllocateFn allocate; /**< Allocate function pointer.*/ + PalFreeFn free; /**< Free function pointer.*/ + void* userData; /**< Optional user-provided data. Can be `nullptr`.*/ +} PalAllocator; + +/** + * @struct PalLogger + * @brief Logging configuration. + * + * Provides a callback and user data for handling log messages. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalLogCallback callback; /**< Callback function pointer.*/ + void* userData; /** Optional user-provided data. Can be `nullptr`.*/ +} PalLogger; + +/** + * Convert a result value to a human-readable string. + * + * This returns a null-terminated string. The string is truncated if `bufferSize` is insufficient. + * + * @param result The PalResult value to format. + * @param bufferSize The size of the buffer. + * @param buffer The buffer. + * + * Thread safety: Thread safe if buffer is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palFormatResult( + PalResult result, + uint64_t bufferSize, + char* buffer); + +/** + * Retrieve the PAL version number. + * + * @param version Pointer to PalVersion struct to fill. + * + * Thread safety: Thread safe if version is per thread. + * + * @since 2.0 + * @sa palGetVersionString + */ +PAL_API void PAL_CALL palGetVersion(PalVersion* version); + +/** + * Retrieve the PAL version string. + * + * @return Null-terminated string containing the PAL version. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palGetVersion + */ +PAL_API const char* PAL_CALL palGetVersionString(); + +/** + * Allocate memory using a custom or default allocator. + * + * @param allocator The allocator to use. Set to `nullptr` to use default. + * @param size Number of bytes to allocate. + * @param alignment Must be power of two. Set to 0 to use implementation-defined default. + * + * @return Pointer to allocated memory on success, or `nullptr` on failure. + * + * Thread safety: Thread safe if the provided allocator is thread safe. The default allocator + * is thread safe. + * + * @since 2.0 + * @sa palFree + */ +PAL_API void* PAL_CALL palAllocate( + const PalAllocator* allocator, + uint64_t size, + uint64_t alignment); + +/** + * Free memory allocated by palAllocate. + * + * @param allocator The allocator used to allocate the memory. Set to `nullptr` to use default. + * @param ptr Pointer to memory to free. + * + * Thread safety: Thread safe if the provided allocator is thread + * safe. The default allocator is thread safe. + * + * @since 2.0 + * @sa palAllocate + */ +PAL_API void PAL_CALL palFree( + const PalAllocator* allocator, + void* ptr); + +/** + * Log a formatted message. + * + * @param logger Logger instance. Set to `nullptr` to use default logger. + * @param fmt printf-style format string. + * @param ... Arguments for the format string. + * + * Thread safety: Thread safe, but log output and + * callbacks may be invoked concurrently. The user must ensure the callback + * implementation is thread safe. + * + * @since 2.0 + * @sa palFormatResult + */ +PAL_API void PAL_CALL palLog( + const PalLogger* logger, + const char* fmt, + ...); + +/** + * Query a high-resolution performance counter value. + * + * @return Current performance counter value. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palGetPerformanceFrequency + */ +PAL_API uint64_t PAL_CALL palGetPerformanceCounter(); + +/** + * Query the frequency of the high-resolution performance counter. + * + * @return Performance counter frequency, in counts per second. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palGetPerformanceCounter + */ +PAL_API uint64_t PAL_CALL palGetPerformanceFrequency(); + +/** + * Get the result code from the result value. + * + * @param result The result value. + * + * @return The result code from the result value. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +static inline PalResultCode PAL_CALL palGetResultCode(PalResult result) +{ + return (uint16_t)(result & 0xFFFFU); +} + +/** + * Get the result source from the result value. + * + * @param result The result value. + * + * @return The result source from the result value. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +static inline PalResultSource PAL_CALL palGetResultSource(PalResult result) +{ + return (uint16_t)((result >> 16) & 0xFFFFu); +} + +/** + * Get the result native code from the result value. + * + * @param result The result value. + * + * @return The result native code from the result value. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +static inline uint32_t PAL_CALL palGetResultNativeCode(PalResult result) +{ + return (uint32_t)(result >> 32); +} + +/** + * Create a `PalResult` value. + * + * @param code The result code. + * @param source The result source. + * @param nativeCode The result native code. + * + * @return The created `PalResult` value. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +static inline PalResult PAL_CALL palMakeResult( + PalResultCode code, + PalResultSource source, + uint32_t nativeCode) +{ + return ((uint64_t)nativeCode << 32) | ((uint64_t)source << 16) | (uint64_t)code; +} + +/** + * @brief Combine two 32-bit unsigned integers into a single 64-bit unsigned integer. + * + * @return The combined 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palUnpackUint32 + */ +static inline uint64_t PAL_CALL palPackUint32( + uint32_t low, + uint32_t high) +{ + return (uint64_t)(((uint64_t)high << 32) | (uint64_t)low); +} + +/** + * @brief Combine two 32-bit signed integers into a single 64-bit unsigned integer. + * + * @return The combined 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palUnpackInt32 + */ +static inline uint64_t PAL_CALL palPackInt32( + int32_t low, + int32_t high) +{ + return ((uint64_t)(uint32_t)high << 32) | (uint32_t)low; +} + +/** + * @brief Pack a pointer into a 64-bit unsigned integer. + * + * @return The packed 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palUnpackPointer + */ +static inline uint64_t PAL_CALL palPackPointer(void* ptr) +{ + return (uint64_t)(uintptr_t)ptr; +} + +/** + * @brief Combine two floats into a single 64-bit unsigned integer. + * + * @return The combined 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palUnpackFloat + */ +static inline uint64_t PAL_CALL palPackFloat( + float low, + float high) +{ + uint64_t combined = 0; +#if PAL_BIG_ENDIAN + memcpy(&((uint32_t*)&combined)[0], &high, sizeof(float)); + memcpy(&((uint32_t*)&combined)[1], &low, sizeof(float)); +#else + memcpy(&((uint32_t*)&combined)[0], &low, sizeof(float)); + memcpy(&((uint32_t*)&combined)[1], &high, sizeof(float)); +#endif // PAL_BIG_ENDIAN + + return combined; +} + +/** + * @brief Retrieve two 32-bit unsigned integers from a 64-bit unsigned integer. + * + * @param[out] outLow Low value of the 64-bit unsigned integer. + * @param[out] outHigh High value of the 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palPackUint32 + */ +static inline void PAL_CALL palUnpackUint32( + uint64_t data, + uint32_t* outLow, + uint32_t* outHigh) +{ + if (outLow) { + *outLow = (uint32_t)(data & 0xFFFFFFFF); + } + + if (outHigh) { + *outHigh = (uint32_t)((uint64_t)data >> 32); + } +} + +/** + * @brief Retrieve two 32-bit signed integers from a 64-bit unsigned integer. + * + * @param[out] outLow Low value of the 64-bit unsigned integer. + * @param[out] outHigh High value of the 64-bit unsigned integer. + * + * Thread safety: Thread-safe if `outLow` and `outHigh` are + * thread local. + * + * @since 2.0 + * @sa palPackInt32 + */ +static inline void PAL_CALL palUnpackInt32( + uint64_t data, + int32_t* outLow, + int32_t* outHigh) +{ + if (outLow) { + *outLow = (int32_t)(data & 0xFFFFFFFF); + } + + if (outHigh) { + *outHigh = (int32_t)((uint64_t)data >> 32); + } +} + +/** + * @brief Unpack a pointer from a 64-bit unsigned integer. + * + * @return The pointer from the 64-bit unsigned integer. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palPackPointer + */ +static inline void* PAL_CALL palUnpackPointer(uint64_t data) +{ + return (void*)(uintptr_t)data; +} + +/** + * @brief Retrieve two floats from a 64-bit unsigned integer. + * + * @param[out] outLow Low value of the 64-bit unsigned integer. + * @param[out] outHigh High value of the 64-bit unsigned integer. + * + * Thread safety: Thread-safe if `outLow` and `outHigh` are + * thread local. + * + * @since 2.0 + * @sa palPackFloat + */ +static inline void PAL_CALL palUnpackFloat( + uint64_t data, + float* low, + float* high) +{ +#if PAL_BIG_ENDIAN + if (low) { + memcpy(low, &((uint32_t*)&data)[1], sizeof(float)); + } + + if (high) { + memcpy(high, &((uint32_t*)&data)[0], sizeof(float)); + } +#else + if (low) { + memcpy(low, &((uint32_t*)&data)[0], sizeof(float)); + } + + if (high) { + memcpy(high, &((uint32_t*)&data)[1], sizeof(float)); + } + +#endif // PAL_BIG_ENDIAN +} + +/** @} */ + +#endif // _PAL_CORE_H diff --git a/include/pal2/pal_event.h b/include/pal2/pal_event.h new file mode 100644 index 00000000..24f6115c --- /dev/null +++ b/include/pal2/pal_event.h @@ -0,0 +1,603 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_event Event + * @ingroup pal_event + * @{ + */ + +#ifndef _PAL_EVENT_H +#define _PAL_EVENT_H + +#include "pal2/pal_core.h" + +#define PAL_DECORATION_MODE_CLIENT_SIDE 0 +#define PAL_DECORATION_MODE_SERVER_SIDE 1 +#define PAL_DECORATION_MODE_COUNT 2 + +/** + * PAL_EVENT_TYPE_WINDOW_CLOSE + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_CLOSE 0 + +/** + * PAL_EVENT_TYPE_WINDOW_SIZE + * + * data: + * bits 0-31: width + * bits 32-63: height + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &width, &height) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_SIZE 1 + +/** + * PAL_EVENT_TYPE_WINDOW_MOVE + * + * data: + * bits 0-31: x + * bits 32-63: y + * + * data2: + * window + * + * Helpers: + * palUnpackInt32(event.data, &x, &y) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_MOVE 2 + +/** + * PAL_EVENT_TYPE_WINDOW_STATE + * + * data: + * bits 0-31: state (minimized, maximized, restored). + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_STATE 3 + +/** + * PAL_EVENT_TYPE_WINDOW_FOCUS + * + * data: + * bits 0-31: focus gained (`PAL_TRUE`/`PAL_FALSE`). + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_FOCUS 4 + +/** + * PAL_EVENT_TYPE_WINDOW_VISIBILITY + * + * data: + * bits 0-31: visibility (`PAL_TRUE`/`PAL_FALSE`). + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_VISIBILITY 5 + +/** + * PAL_EVENT_TYPE_WINDOW_MODAL_BEGIN + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_MODAL_BEGIN 6 + +/** + * PAL_EVENT_TYPE_WINDOW_MODAL_END + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_MODAL_END 7 + +/** + * PAL_EVENT_TYPE_MONITOR_DPI_CHANGED + * + * data: + * bits 0-31: dpi + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MONITOR_DPI_CHANGED 8 + +/** + * PAL_EVENT_TYPE_MONITOR_LIST_CHANGED + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MONITOR_LIST_CHANGED 9 + +/** + * PAL_EVENT_TYPE_KEYDOWN + * + * data: + * bits 0-31: keycode + * bits 32-63: scancode + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &keycode, &scancode) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_KEYDOWN 10 + +/** + * PAL_EVENT_TYPE_KEYREPEAT + * + * data: + * bits 0-31: keycode + * bits 32-63: scancode + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &keycode, &scancode) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_KEYREPEAT 11 + +/** + * PAL_EVENT_TYPE_KEYUP + * + * data: + * bits 0-31: keycode + * bits 32-63: scancode + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &keycode, &scancode) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_KEYUP 12 + +/** + * PAL_EVENT_TYPE_MOUSE_BUTTONDOWN + * + * data: + * bits 0-31: button + * bits 32-63: serial + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &button, &serial) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MOUSE_BUTTONDOWN 13 + +/** + * PAL_EVENT_TYPE_MOUSE_BUTTONUP + * + * data: + * bits 0-31: button + * bits 32-63: serial + * + * data2: + * window + * + * Helpers: + * palUnpackUint32(event.data, &button, &serial) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MOUSE_BUTTONUP 14 + +/** + * PAL_EVENT_TYPE_MOUSE_MOVE + * + * data: + * bits 0-31: x + * bits 32-63: y + * + * data2: + * window + * + * Helpers: + * palUnpackInt32(event.data, &x, &y) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MOUSE_MOVE 15 + +/** + * PAL_EVENT_TYPE_MOUSE_DELTA + * + * data: + * bits 0-31: dx + * bits 32-63: dy + * + * data2: + * window + * + * Helpers: + * palUnpackFloat(event.data, &dx, &dy) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MOUSE_DELTA 16 + +/** + * PAL_EVENT_TYPE_MOUSE_WHEEL + * + * data: + * bits 0-31: dx + * bits 32-63: dy + * + * data2: + * window + * + * Helpers: + * palUnpackFloat(event.data, &dx, &dy) + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_MOUSE_WHEEL 17 + +/** + * PAL_EVENT_TYPE_USER + * + * userId: + * User event ID or type. + * + * Helpers: + * palPackInt32() + * palPackUint32() + * palPackPointer() + * palUnpackInt32() + * palUnpackUint32() + * palUnpackPointer() + */ +#define PAL_EVENT_TYPE_USER 18 + +/** + * PAL_EVENT_TYPE_KEYCHAR + * + * data: + * bits 0-31: codepoint + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_KEYCHAR 19 + +/** + * PAL_EVENT_TYPE_WINDOW_DECORATION_MODE + * + * data: + * bits 0-31: decorations mode + * bits 32-63: unused + * + * data2: + * window + * + * Helpers: + * window = palUnpackPointer(event.data2) + */ +#define PAL_EVENT_TYPE_WINDOW_DECORATION_MODE 20 + +#define PAL_EVENT_TYPE_COUNT 21 + +#define PAL_DISPATCH_MODE_NONE 0 +#define PAL_DISPATCH_MODE_CALLBACK 1 +#define PAL_DISPATCH_MODE_POLL 2 +#define PAL_DISPATCH_MODE_COUNT 3 + +/** + * @struct PalEventDriver + * @brief Opaque handle to an event driver. + * + * @since 2.0 + */ +typedef struct PalEventDriver PalEventDriver; + +/** + * @struct PalEvent + * @brief A single event. + * + * @since 2.0 + */ +typedef struct PalEvent PalEvent; + +/** + * @typedef PalDecorationMode + * @brief Decoration types. + * + * All decoration types follow the format `PAL_DECORATION_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDecorationMode; + +/** + * @typedef PalEventType + * @brief Event types. + * + * All event types follow the format `PAL_EVENT_TYPE_**` for consistency and + * API use. + * + * @since 2.0 + */ +typedef uint32_t PalEventType; + +/** + * @typedef PalDispatchMode + * @brief Dispatch modes for an event. + * + * All dispatch modes follow the format `PAL_DISPATCH_MODE_**` for consistency + * and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDispatchMode; + +/** + * @typedef PalEventCallback + * @brief Function pointer type used for event callbacks. + * + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * @param[in] event Pointer to the event. + * + * @since 2.0 + * @sa PalPushFn + */ +typedef void(PAL_CALL* PalEventCallback)( + void* userData, + const PalEvent* event); + +/** + * @typedef PalPushFn + * @brief Function pointer type used for pushing events into event queues. + * + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * @param[in] event Pointer to the event to push. + * + * @since 2.0 + * @sa PalEventCallback + */ +typedef void(PAL_CALL* PalPushFn)( + void* userData, + PalEvent* event); + +/** + * @typedef PalPollFn + * @brief Function pointer type used for polling events from event queues. + * + * This function should return `PAL_FALSE` if the event queue is empty. + * If the queue is not empty and the event was retrieved, this should return + * `PAL_TRUE`. + * + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * @param[out] outEvent Pointer to the PalEvent to recieve the event. + * + * @since 2.0 + * @sa PalPushFn + */ +typedef PalBool(PAL_CALL* PalPollFn)( + void* userData, + PalEvent* outEvent); + +struct PalEvent { + uint64_t data; /**< First data payload.*/ + uint64_t data2; /**< Second data payload.*/ + uint32_t userId; /**< User event id.*/ + PalEventType type; /**< (eg. `PAL_EVENT_TYPE_WINDOW_MOVE`).*/ +}; + +/** + * @struct PalEventQueue + * @brief Custom event queue. + * + * Provides user-defined event queue push and poll functions. + * + * @since 2.0 + */ +typedef struct { + PalPushFn push; /**< Push function pointer.*/ + PalPollFn poll; /**< Poll function pointer.*/ + void* userData; /**< Optional user-provided data. Can be `nullptr`.*/ +} PalEventQueue; + +/** + * @struct PalEventDriverCreateInfo + * @brief Creation parameters for an event driver. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const PalAllocator* allocator; /**< Set to `nullptr` to use default.*/ + PalEventQueue* queue; /**< Set to `nullptr` to use default.*/ + PalEventCallback callback; /**< Can be `nullptr`.*/ + void* userData; /**< Optional user-provided data. Can be `nullptr`.*/ +} PalEventDriverCreateInfo; + +/** + * @brief Create an event driver. + * + * The allocator field in the provided PalEventDriverCreateInfo struct will not + * be copied, therefore the pointer must remain valid until the event driver is + * destroyed. Destroy the event driver with `palDestroyEventDriver()` when no + * longer needed. + * + * @param[in] info Pointer to a PalEventDriverCreateInfo struct that specifies + * parameters. + * @param[out] outEventDriver Pointer to a PalEventDriver to recieve the created + * event driver. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if the provided allocator is + * thread safe and `outEventDriver` is per thread. The default allocator is + * thread safe. + * + * @since 2.0 + * @sa palDestroyEventDriver + */ +PAL_API PalResult PAL_CALL palCreateEventDriver( + const PalEventDriverCreateInfo* info, + PalEventDriver** outEventDriver); + +/** + * @brief Destroy the provided event driver. + * + * @param[in] eventDriver Pointer to the event driver to destroy. + * + * Thread safety: Thread safe if the allocator used to create + * the event driver is thread safe and `eventDriver` is per thread. + * + * @since 2.0 + * @sa palCreateEventDriver + */ +PAL_API void PAL_CALL palDestroyEventDriver(PalEventDriver* eventDriver); + +/** + * @brief Set the dispatch mode for an event type with the provided event + * driver. + * + * If the dispatch mode is `PAL_DISPATCH_MODE_POLL`, the event will be dispatched + * into the event drivers event queue. If the dispatch mode is + * `PAL_DISPATCH_MODE_CALLBACK`, the event driver must have a valid callback function otherwise + * undefined behavior. + * + * @param[in] eventDriver Pointer to the event driver. + * @param[in] type Event type to set dispatch mode for. + * @param[in] mode Dispatch mode to use. + * + * Thread safety: Thread safe if multiple threads are not + * simultaneously setting dispatch mode on the same `eventDriver`. + * + * @since 2.0 + * @sa palGetEventDispatchMode + */ +PAL_API void PAL_CALL palSetEventDispatchMode( + PalEventDriver* eventDriver, + PalEventType type, + PalDispatchMode mode); + +/** + * @brief Get the dispatch mode for an event type with the provided event + * driver. + * + * @param[in] eventDriver Pointer to the event driver. + * @param[in] type The event type. + * + * @return The dispatch mode on success or `PAL_DISPATCH_MODE_NONE` on failure. + * + * Thread safety: Thread safe if multiple threads are not + * simultaneously setting dispatch mode on the same `eventDriver`. + * + * @since 2.0 + * @sa palSetEventDispatchMode + */ +PAL_API PalDispatchMode PAL_CALL palGetEventDispatchMode( + PalEventDriver* eventDriver, + PalEventType type); + +/** + * @brief Push an event into the queue or callback function of the provided + * event driver. + * + * If the dispatch mode for the event is `PAL_DISPATCH_MODE_POLL`, the event will be + * pushed to the event queue. + * + * If dispatch mode is `PAL_DISPATCH_MODE_CALLBACK`, the event driver must have a valid callback + * function otherwise undefined behavior. + * + * @param[in] eventDriver Pointer to the event driver. + * @param[in] event Pointer to the event to push. + * + * Thread safety: Thread safe if the provided event queue is thread + * safe or every thread has its own `eventDriver`. The default event queue is + * not thread safe. + * + * @since 2.0 + * @sa palPollEvent + */ +PAL_API void PAL_CALL palPushEvent( + PalEventDriver* eventDriver, + PalEvent* event); + +/** + * @brief Retrieve the next available event from the queue of the provided event + * driver. + * + * This function retrieves the next pending event from the queue of the + * provided event driver without blocking. If no events are available, it + * returns `PAL_FALSE`. + * + * @param[in] eventDriver Pointer to the event driver. + * @param[out] outEvent Pointer to a PalEvent to recieve the event. Must be + * valid. + * + * Thread safety: Thread safe if the provided event queue is thread + * safe or every thread has its own `eventDriver`. The default event queue is + * not thread safe. + * + * @since 2.0 + * @sa palPushEvent + */ +PAL_API PalBool PAL_CALL palPollEvent( + PalEventDriver* eventDriver, + PalEvent* outEvent); + +/** @} */ + +#endif // _PAL_EVENT_H diff --git a/include/pal2/pal_graphics.h b/include/pal2/pal_graphics.h new file mode 100644 index 00000000..c659ad12 --- /dev/null +++ b/include/pal2/pal_graphics.h @@ -0,0 +1,7086 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_graphics Graphics + * @ingroup pal_graphics + * @{ + */ + +#ifndef _PAL_GRAPHICS_H +#define _PAL_GRAPHICS_H + +#include "pal_core.h" + +#define PAL_ADAPTER_NAME_SIZE 128 +#define PAL_ADAPTER_BACKEND_NAME_SIZE 32 +#define PAL_SHADER_ENTRY_NAME_SIZE 32 +#define PAL_UNUSED_SHADER_INDEX UINT32_MAX +#define PAL_MAX_CUSTOM_BACKENDS 16 + +#define PAL_MAKE_SHADER_TARGET(major, minor) ((uint32_t)((major) << 8) | (minor)) +#define PAL_SHADER_TARGET_MAJOR(target) ((uint32_t)(target) >> 8); +#define PAL_SHADER_TARGET_MINOR(target) ((uint32_t)(target) & 0xFF); + +#define PAL_ADAPTER_FEATURE_NONE 0 +#define PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY (1ULL << 1) +#define PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING (1ULL << 2) +#define PAL_ADAPTER_FEATURE_MULTI_VIEWPORT (1ULL << 3) +#define PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE (1ULL << 4) +#define PAL_ADAPTER_FEATURE_TESSELLATION_SHADER (1ULL << 5) +#define PAL_ADAPTER_FEATURE_GEOMETRY_SHADER (1ULL << 6) +#define PAL_ADAPTER_FEATURE_SHADER_FLOAT16 (1ULL << 7) +#define PAL_ADAPTER_FEATURE_SHADER_FLOAT64 (1ULL << 8) +#define PAL_ADAPTER_FEATURE_SHADER_INT16 (1ULL << 9) +#define PAL_ADAPTER_FEATURE_SHADER_INT64 (1ULL << 10) +#define PAL_ADAPTER_FEATURE_RAY_TRACING (1ULL << 11) +#define PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING (1ULL << 12) +#define PAL_ADAPTER_FEATURE_MESH_SHADER (1ULL << 13) +#define PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE (1ULL << 14) +#define PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING (1ULL << 15) +#define PAL_ADAPTER_FEATURE_SWAPCHAIN (1ULL << 16) +#define PAL_ADAPTER_FEATURE_MULTI_VIEW (1ULL << 17) +#define PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY (1ULL << 18) +#define PAL_ADAPTER_FEATURE_FENCE_RESET (1ULL << 19) +#define PAL_ADAPTER_FEATURE_POLYGON_MODE_LINE (1ULL << 20) +#define PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE (1ULL << 21) +#define PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE (1ULL << 22) +#define PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY (1ULL << 23) +#define PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE (1ULL << 24) +#define PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE (1ULL << 25) +#define PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP (1ULL << 26) +#define PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE (1ULL << 27) +#define PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT (1ULL << 28) +#define PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS (1ULL << 29) +#define PAL_ADAPTER_FEATURE_INDIRECT_DRAW (1ULL << 30) +#define PAL_ADAPTER_FEATURE_INDIRECT_DISPATCH (1ULL << 31) +#define PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT (1ULL << 32) +#define PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH (1ULL << 33) +#define PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT (1ULL << 34) +#define PAL_ADAPTER_FEATURE_DISPATCH_BASE (1ULL << 35) +#define PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS (1ULL << 36) +#define PAL_ADAPTER_FEATURE_RAY_QUERY (1ULL << 37) + +#define PAL_ADAPTER_TYPE_UNKNOWN 0 +#define PAL_ADAPTER_TYPE_DISCRETE 1 +#define PAL_ADAPTER_TYPE_INTEGRATED 2 +#define PAL_ADAPTER_TYPE_VIRTUAL 3 +#define PAL_ADAPTER_TYPE_CPU 4 +#define PAL_ADAPTER_TYPE_COUNT 5 + +#define PAL_ADAPTER_API_TYPE_UNKNOWN 0 +#define PAL_ADAPTER_API_TYPE_VULKAN 1 +#define PAL_ADAPTER_API_TYPE_D3D12 2 +#define PAL_ADAPTER_API_TYPE_METAL 3 +#define PAL_ADAPTER_API_TYPE_D3D11 4 +#define PAL_ADAPTER_API_TYPE_D3D9 5 +#define PAL_ADAPTER_API_TYPE_OPENGL 6 +#define PAL_ADAPTER_API_TYPE_OPENGLES 7 +#define PAL_ADAPTER_API_TYPE_WEBGPU 8 +#define PAL_ADAPTER_API_TYPE_COUNT 9 + +#define PAL_QUEUE_TYPE_GRAPHICS 0 +#define PAL_QUEUE_TYPE_COMPUTE 1 +#define PAL_QUEUE_TYPE_COPY 2 +#define PAL_QUEUE_TYPE_COUNT 3 + +#define PAL_PRESENT_MODE_FIFO 0 /**< V-Sync.*/ +#define PAL_PRESENT_MODE_IMMEDIATE 1 +#define PAL_PRESENT_MODE_MAILBOX 2 +#define PAL_PRESENT_MODE_COUNT 3 + +#define PAL_COMPOSITE_ALPHA_OPAQUE 0 +#define PAL_COMPOSITE_ALPHA_PRE_MULTIPLIED 1 +#define PAL_COMPOSITE_ALPHA_POST_MULTIPLIED 2 +#define PAL_COMPOSITE_ALPHA_COUNT 3 + +#define PAL_FORMAT_UNDEFINED 0 +#define PAL_FORMAT_R8_UNORM 1 +#define PAL_FORMAT_R8_SNORM 2 +#define PAL_FORMAT_R8_UINT 3 +#define PAL_FORMAT_R8_SINT 4 +#define PAL_FORMAT_R8_SRGB 5 +#define PAL_FORMAT_R16_UNORM 6 +#define PAL_FORMAT_R16_SNORM 7 +#define PAL_FORMAT_R16_UINT 8 +#define PAL_FORMAT_R16_SINT 9 +#define PAL_FORMAT_R16_SFLOAT 10 +#define PAL_FORMAT_R32_UINT 11 +#define PAL_FORMAT_R32_SINT 12 +#define PAL_FORMAT_R32_SFLOAT 13 +#define PAL_FORMAT_R64_UINT 14 +#define PAL_FORMAT_R64_SINT 15 +#define PAL_FORMAT_R64_SFLOAT 16 +#define PAL_FORMAT_R8G8_UNORM 17 +#define PAL_FORMAT_R8G8_SNORM 18 +#define PAL_FORMAT_R8G8_UINT 19 +#define PAL_FORMAT_R8G8_SINT 20 +#define PAL_FORMAT_R8G8_SRGB 21 +#define PAL_FORMAT_R16G16_UNORM 22 +#define PAL_FORMAT_R16G16_SNORM 23 +#define PAL_FORMAT_R16G16_UINT 24 +#define PAL_FORMAT_R16G16_SINT 25 +#define PAL_FORMAT_R16G16_SFLOAT 26 +#define PAL_FORMAT_R32G32_UINT 27 +#define PAL_FORMAT_R32G32_SINT 28 +#define PAL_FORMAT_R32G32_SFLOAT 29 +#define PAL_FORMAT_R64G64_UINT 30 +#define PAL_FORMAT_R64G64_SINT 31 +#define PAL_FORMAT_R64G64_SFLOAT 32 +#define PAL_FORMAT_R8G8B8_UNORM 33 +#define PAL_FORMAT_R8G8B8_SNORM 34 +#define PAL_FORMAT_R8G8B8_UINT 35 +#define PAL_FORMAT_R8G8B8_SINT 36 +#define PAL_FORMAT_R8G8B8_SRGB 37 +#define PAL_FORMAT_R16G16B16_UNORM 38 +#define PAL_FORMAT_R16G16B16_SNORM 39 +#define PAL_FORMAT_R16G16B16_UINT 40 +#define PAL_FORMAT_R16G16B16_SINT 41 +#define PAL_FORMAT_R16G16B16_SFLOAT 42 +#define PAL_FORMAT_R32G32B32_UINT 43 +#define PAL_FORMAT_R32G32B32_SINT 44 +#define PAL_FORMAT_R32G32B32_SFLOAT 45 +#define PAL_FORMAT_R64G64B64_UINT 46 +#define PAL_FORMAT_R64G64B64_SINT 47 +#define PAL_FORMAT_R64G64B64_SFLOAT 48 +#define PAL_FORMAT_B8G8R8_UNORM 49 +#define PAL_FORMAT_B8G8R8_SNORM 50 +#define PAL_FORMAT_B8G8R8_UINT 51 +#define PAL_FORMAT_B8G8R8_SINT 52 +#define PAL_FORMAT_B8G8R8_SRGB 53 +#define PAL_FORMAT_R8G8B8A8_UNORM 54 +#define PAL_FORMAT_R8G8B8A8_SNORM 55 +#define PAL_FORMAT_R8G8B8A8_UINT 56 +#define PAL_FORMAT_R8G8B8A8_SINT 57 +#define PAL_FORMAT_R8G8B8A8_SRGB 58 +#define PAL_FORMAT_R16G16B16A16_UNORM 59 +#define PAL_FORMAT_R16G16B16A16_SNORM 60 +#define PAL_FORMAT_R16G16B16A16_UINT 61 +#define PAL_FORMAT_R16G16B16A16_SINT 62 +#define PAL_FORMAT_R16G16B16A16_SFLOAT 63 +#define PAL_FORMAT_R32G32B32A32_UINT 64 +#define PAL_FORMAT_R32G32B32A32_SINT 65 +#define PAL_FORMAT_R32G32B32A32_SFLOAT 66 +#define PAL_FORMAT_R64G64B64A64_UINT 67 +#define PAL_FORMAT_R64G64B64A64_SINT 68 +#define PAL_FORMAT_R64G64B64A64_SFLOAT 69 +#define PAL_FORMAT_B8G8R8A8_UNORM 70 +#define PAL_FORMAT_B8G8R8A8_SNORM 71 +#define PAL_FORMAT_B8G8R8A8_UINT 72 +#define PAL_FORMAT_B8G8R8A8_SINT 73 +#define PAL_FORMAT_B8G8R8A8_SRGB 74 +#define PAL_FORMAT_S8_UINT 75 +#define PAL_FORMAT_D16_UNORM 76 +#define PAL_FORMAT_D32_SFLOAT 77 +#define PAL_FORMAT_D16_UNORM_S8_UINT 78 +#define PAL_FORMAT_D32_SFLOAT_S8_UINT 79 +#define PAL_FORMAT_D24_UNORM_S8_UINT 80 +#define PAL_FORMAT_COUNT 81 + +#define PAL_IMAGE_USAGE_UNDEFINED 0 +#define PAL_IMAGE_USAGE_COLOR_ATTACHEMENT (1U << 0) +#define PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT (1U << 1) +#define PAL_IMAGE_USAGE_TRANSFER_SRC (1U << 2) +#define PAL_IMAGE_USAGE_TRANSFER_DST (1U << 3) +#define PAL_IMAGE_USAGE_STORAGE (1U << 4) +#define PAL_IMAGE_USAGE_SAMPLED (1U << 5) + +#define PAL_SHADER_FORMAT_UNKNOWN 0 +#define PAL_SHADER_FORMAT_SPIRV (1U << 0) +#define PAL_SHADER_FORMAT_DXIL (1U << 1) +#define PAL_SHADER_FORMAT_DXBC (1U << 2) +#define PAL_SHADER_FORMAT_METALLIB (1U << 3) +#define PAL_SHADER_FORMAT_MSL (1U << 4) +#define PAL_SHADER_FORMAT_GLSL (1U << 5) +#define PAL_SHADER_FORMAT_HLSL (1U << 6) +#define PAL_SHADER_FORMAT_WGSL (1U << 7) + +#define PAL_LOAD_OP_LOAD 0 +#define PAL_LOAD_OP_CLEAR 1 +#define PAL_LOAD_OP_DONT_CARE 2 +#define PAL_LOAD_OP_COUNT 3 + +#define PAL_STORE_OP_STORE 0 +#define PAL_STORE_OP_DONT_CARE 1 +#define PAL_STORE_OP_COUNT 2 + +#define PAL_MEMORY_TYPE_GPU_ONLY 0 +#define PAL_MEMORY_TYPE_CPU_UPLOAD 1 +#define PAL_MEMORY_TYPE_CPU_READBACK 2 +#define PAL_MEMORY_TYPE_COUNT 3 + +#define PAL_IMAGE_TYPE_1D 0 +#define PAL_IMAGE_TYPE_2D 1 +#define PAL_IMAGE_TYPE_3D 2 +#define PAL_IMAGE_TYPE_COUNT 3 + +#define PAL_IMAGE_ASPECT_COLOR 0 +#define PAL_IMAGE_ASPECT_DEPTH 1 +#define PAL_IMAGE_ASPECT_STENCIL 2 +#define PAL_IMAGE_ASPECT_DEPTH_STENCIL 3 +#define PAL_IMAGE_ASPECT_COUNT 4 + +#define PAL_IMAGE_VIEW_TYPE_1D 0 +#define PAL_IMAGE_VIEW_TYPE_1D_ARRAY 1 +#define PAL_IMAGE_VIEW_TYPE_2D 2 +#define PAL_IMAGE_VIEW_TYPE_2D_ARRAY 3 +#define PAL_IMAGE_VIEW_TYPE_3D 4 +#define PAL_IMAGE_VIEW_TYPE_CUBE 5 +#define PAL_IMAGE_VIEW_TYPE_CUBE_ARRAY 6 +#define PAL_IMAGE_VIEW_TYPE_COUNT 7 + +#define PAL_FILTER_MODE_NEAREST 0 +#define PAL_FILTER_MODE_LINEAR 1 +#define PAL_FILTER_MODE_COUNT 2 + +#define PAL_SAMPLER_MIPMAP_MODE_NEAREST 0 +#define PAL_SAMPLER_MIPMAP_MODE_LINEAR 1 +#define PAL_SAMPLER_MIPMAP_MODE_COUNT 2 + +#define PAL_SAMPLER_ADDRESS_MODE_REPEAT 0 +#define PAL_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT 1 +#define PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE 2 +#define PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER 3 +#define PAL_SAMPLER_ADDRESS_MODE_COUNT 4 + +#define PAL_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK 0 +#define PAL_BORDER_COLOR_INT_TRANSPARENT_BLACK 1 +#define PAL_BORDER_COLOR_FLOAT_OPAQUE_BLACK 2 +#define PAL_BORDER_COLOR_INT_OPAQUE_BLACK 3 +#define PAL_BORDER_COLOR_FLOAT_OPAQUE_WHITE 4 +#define PAL_BORDER_COLOR_INT_OPAQUE_WHITE 5 +#define PAL_BORDER_COLOR_COUNT 6 + +#define PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR 0 +#define PAL_SURFACE_FORMAT_BGRA8_SRGB_NONLINEAR 1 +#define PAL_SURFACE_FORMAT_RGBA8_UNORM_SRGB_NONLINEAR 2 +#define PAL_SURFACE_FORMAT_RGBA16_FLOAT_HDR10 3 +#define PAL_SURFACE_FORMAT_COUNT 4 + +#define PAL_WINDOW_INSTANCE_TYPE_WAYLAND 0 +#define PAL_WINDOW_INSTANCE_TYPE_X11 1 +#define PAL_WINDOW_INSTANCE_TYPE_XCB 2 +#define PAL_WINDOW_INSTANCE_TYPE_WIN32 3 +#define PAL_WINDOW_INSTANCE_TYPE_COUNT 4 + +#define PAL_SHADER_STAGE_UNDEFINED 0 +#define PAL_SHADER_STAGE_VERTEX 1 +#define PAL_SHADER_STAGE_FRAGMENT 2 +#define PAL_SHADER_STAGE_COMPUTE 3 +#define PAL_SHADER_STAGE_GEOMETRY 4 +#define PAL_SHADER_STAGE_MESH 5 +#define PAL_SHADER_STAGE_TASK 6 +#define PAL_SHADER_STAGE_TESSELLATION_CONTROL 7 +#define PAL_SHADER_STAGE_TESSELLATION_EVALUATION 8 +#define PAL_SHADER_STAGE_RAYGEN 9 +#define PAL_SHADER_STAGE_CLOSEST_HIT 10 +#define PAL_SHADER_STAGE_ANY_HIT 11 +#define PAL_SHADER_STAGE_MISS 12 +#define PAL_SHADER_STAGE_INTERSECTION 13 +#define PAL_SHADER_STAGE_CALLABLE 14 +#define PAL_SHADER_STAGE_COUNT 15 + +#define PAL_SAMPLE_COUNT_1 0 +#define PAL_SAMPLE_COUNT_2 1 +#define PAL_SAMPLE_COUNT_4 2 +#define PAL_SAMPLE_COUNT_8 3 +#define PAL_SAMPLE_COUNT_16 4 +#define PAL_SAMPLE_COUNT_32 5 +#define PAL_SAMPLE_COUNT_64 6 +#define PAL_SAMPLE_COUNT_COUNT 7 /**< Name redundancy is intentionally consistent.*/ + +#define PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST 0 +#define PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP 1 +#define PAL_PRIMITIVE_TOPOLOGY_LINE_LIST 2 +#define PAL_PRIMITIVE_TOPOLOGY_LINE_STRIP 3 +#define PAL_PRIMITIVE_TOPOLOGY_POINT_LIST 4 +#define PAL_PRIMITIVE_TOPOLOGY_PATCH 5 +#define PAL_PRIMITIVE_TOPOLOGY_COUNT 6 + +#define PAL_CULL_MODE_NONE 0 +#define PAL_CULL_MODE_FRONT 1 +#define PAL_CULL_MODE_BACK 2 +#define PAL_CULL_MODE_COUNT 3 + +#define PAL_FRONT_FACE_CLOCKWISE 0 +#define PAL_FRONT_FACE_COUNTER_CLOCKWISE 1 +#define PAL_FRONT_FACE_COUNT 2 + +#define PAL_POLYGON_MODE_FILL 0 +#define PAL_POLYGON_MODE_LINE 1 +#define PAL_POLYGON_MODE_COUNT 2 + +#define PAL_STENCIL_FACE_FLAG_FRONT (1U << 0) +#define PAL_STENCIL_FACE_FLAG_BACK (1U << 1) +#define PAL_STENCIL_FACE_FLAG_BOTH (PAL_STENCIL_FACE_FLAG_FRONT | PAL_STENCIL_FACE_FLAG_BACK) + +#define PAL_VERTEX_TYPE_UNDEFINED 0 +#define PAL_VERTEX_TYPE_INT32 1 +#define PAL_VERTEX_TYPE_INT32_2 2 +#define PAL_VERTEX_TYPE_INT32_3 3 +#define PAL_VERTEX_TYPE_INT32_4 4 +#define PAL_VERTEX_TYPE_UINT32 5 +#define PAL_VERTEX_TYPE_UINT32_2 6 +#define PAL_VERTEX_TYPE_UINT32_3 7 +#define PAL_VERTEX_TYPE_UINT32_4 8 +#define PAL_VERTEX_TYPE_INT8_2 9 +#define PAL_VERTEX_TYPE_INT8_4 10 +#define PAL_VERTEX_TYPE_UINT8_2 11 +#define PAL_VERTEX_TYPE_UINT8_4 12 +#define PAL_VERTEX_TYPE_INT8_2NORM 13 +#define PAL_VERTEX_TYPE_INT8_4NORM 14 +#define PAL_VERTEX_TYPE_UINT8_2NORM 15 +#define PAL_VERTEX_TYPE_UINT8_4NORM 16 +#define PAL_VERTEX_TYPE_INT16_2 17 +#define PAL_VERTEX_TYPE_INT16_4 18 +#define PAL_VERTEX_TYPE_UINT16_2 19 +#define PAL_VERTEX_TYPE_UINT16_4 20 +#define PAL_VERTEX_TYPE_INT16_2NORM 21 +#define PAL_VERTEX_TYPE_INT16_4NORM 22 +#define PAL_VERTEX_TYPE_UINT16_2NORM 23 +#define PAL_VERTEX_TYPE_UINT16_4NORM 24 +#define PAL_VERTEX_TYPE_FLOAT 25 +#define PAL_VERTEX_TYPE_FLOAT2 26 +#define PAL_VERTEX_TYPE_FLOAT3 27 +#define PAL_VERTEX_TYPE_FLOAT4 28 +#define PAL_VERTEX_TYPE_HALF_FLOAT16_2 29 +#define PAL_VERTEX_TYPE_HALF_FLOAT16_4 30 +#define PAL_VERTEX_TYPE_COUNT 31 + +#define PAL_VERTEX_SEMANTIC_ID_POSITION 0 +#define PAL_VERTEX_SEMANTIC_ID_COLOR 1 +#define PAL_VERTEX_SEMANTIC_ID_TEXCOORD 2 +#define PAL_VERTEX_SEMANTIC_ID_NORMAL 3 +#define PAL_VERTEX_SEMANTIC_ID_TANGENT 4 +#define PAL_VERTEX_SEMANTIC_ID_COUNT 5 + +#define PAL_COMMAND_BUFFER_TYPE_PRIMARY 0 +#define PAL_COMMAND_BUFFER_TYPE_SECONDARY 1 +#define PAL_COMMAND_BUFFER_TYPE_COUNT 2 + +#define PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX 0 +#define PAL_VERTEX_LAYOUT_TYPE_PER_INSTANCE 1 +#define PAL_VERTEX_LAYOUT_TYPE_COUNT 2 + +#define PAL_COMPARE_OP_NEVER 0 +#define PAL_COMPARE_OP_LESS 1 +#define PAL_COMPARE_OP_EQUAL 2 +#define PAL_COMPARE_OP_LESS_OR_EQUAL 3 +#define PAL_COMPARE_OP_GREATER 4 +#define PAL_COMPARE_OP_NOT_EQUAL 5 +#define PAL_COMPARE_OP_GREATER_OR_EQUAL 6 +#define PAL_COMPARE_OP_ALWAYS 7 +#define PAL_COMPARE_OP_COUNT 8 + +#define PAL_STENCIL_OP_KEEP 0 +#define PAL_STENCIL_OP_ZERO 1 +#define PAL_STENCIL_OP_REPLACE 2 +#define PAL_STENCIL_OP_INCREMENT_AND_CLAMP 3 +#define PAL_STENCIL_OP_DECREMENT_AND_CLAMP 4 +#define PAL_STENCIL_OP_INVERT 5 +#define PAL_STENCIL_OP_INCREMENT_AND_WRAP 6 +#define PAL_STENCIL_OP_DECREMENT_AND_WRAP 7 +#define PAL_STENCIL_OP_COUNT 8 + +#define PAL_BLEND_OP_ADD 0 +#define PAL_BLEND_OP_SUBTRACT 1 +#define PAL_BLEND_OP_REVERSE_SUBTRACT 2 +#define PAL_BLEND_OP_MIN 3 +#define PAL_BLEND_OP_MAX 4 +#define PAL_BLEND_OP_COUNT 5 + +#define PAL_BLEND_FACTOR_ZERO 0 +#define PAL_BLEND_FACTOR_ONE 1 +#define PAL_BLEND_FACTOR_SRC_COLOR 2 +#define PAL_BLEND_FACTOR_ONE_MINUS_SRC_COLOR 3 +#define PAL_BLEND_FACTOR_DST_COLOR 4 +#define PAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR 5 +#define PAL_BLEND_FACTOR_SRC_ALPHA 6 +#define PAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA 7 +#define PAL_BLEND_FACTOR_DST_ALPHA 8 +#define PAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA 9 +#define PAL_BLEND_FACTOR_CONSTANT_COLOR 10 +#define PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR 11 +#define PAL_BLEND_FACTOR_CONSTANT_ALPHA 12 +#define PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA 13 +#define PAL_BLEND_FACTOR_COUNT 14 + +#define PAL_COLOR_MASK_NONE 0 +#define PAL_COLOR_MASK_RED (1U << 0) +#define PAL_COLOR_MASK_GREEN (1U << 1) +#define PAL_COLOR_MASK_BLUE (1U << 2) +#define PAL_COLOR_MASK_ALPHA (1U << 3) + +#define PAL_RESOLVE_MODE_NONE 0 +#define PAL_RESOLVE_MODE_SAMPLE_ZERO 1 +#define PAL_RESOLVE_MODE_AVERAGE 2 +#define PAL_RESOLVE_MODE_MIN 3 +#define PAL_RESOLVE_MODE_MAX 4 +#define PAL_RESOLVE_MODE_COUNT 5 + +#define PAL_FRAGMENT_SHADING_RATE_1X1 0 +#define PAL_FRAGMENT_SHADING_RATE_1X2 1 +#define PAL_FRAGMENT_SHADING_RATE_2X1 2 +#define PAL_FRAGMENT_SHADING_RATE_2X2 3 +#define PAL_FRAGMENT_SHADING_RATE_2X4 4 +#define PAL_FRAGMENT_SHADING_RATE_4X2 5 +#define PAL_FRAGMENT_SHADING_RATE_4X4 6 +#define PAL_FRAGMENT_SHADING_RATE_COUNT 7 + +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP 0 +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE 1 +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN 2 +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX 3 +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL 4 +#define PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_COUNT 5 + +#define PAL_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL 0 +#define PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL 1 +#define PAL_ACCELERATION_STRUCTURE_TYPE_COUNT 2 + +#define PAL_ACCELERATION_STRUCTURE_BUILD_MODE_BUILD 0 +#define PAL_ACCELERATION_STRUCTURE_BUILD_MODE_UPDATE 1 +#define PAL_ACCELERATION_STRUCTURE_BUILD_MODE_COUNT 2 + +#define PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_BUILD (1U << 0) +#define PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_TRACE (1U << 1) +#define PAL_ACCELERATION_STRUCTURE_BUILD_HINT_LOW_MEMORY (1U << 2) + +#define PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_OPAQUE (1U << 0) +#define PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_NO_OPAQUE (1U << 1) +#define PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FACING_CULL_DISABLE (1U << 2) +#define PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FRONT_COUNTERCLOCKWISE (1U << 3) + +#define PAL_GEOMETRY_TYPE_TRIANGLE 0 +#define PAL_GEOMETRY_TYPE_AABBS 1 +#define PAL_GEOMETRY_TYPE_COUNT 2 + +#define PAL_GEOMETRY_FLAG_OPAQUE (1U << 0) +#define PAL_GEOMETRY_FLAG_NO_DUPLICATE_ANYHIT (1U << 1) + +#define PAL_INDEX_TYPE_UINT16 0 +#define PAL_INDEX_TYPE_UINT32 1 +#define PAL_INDEX_TYPE_COUNT 2 + +#define PAL_BUFFER_USAGE_VERTEX (1U << 0) +#define PAL_BUFFER_USAGE_INDEX (1U << 1) +#define PAL_BUFFER_USAGE_UNIFORM (1U << 2) +#define PAL_BUFFER_USAGE_STORAGE (1U << 3) +#define PAL_BUFFER_USAGE_TRANSFER_SRC (1U << 4) +#define PAL_BUFFER_USAGE_TRANSFER_DST (1U << 5) +#define PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE (1U << 6) +#define PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH (1U << 7) +#define PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_READ_ONLY_INPUT (1U << 8) +#define PAL_BUFFER_USAGE_DEVICE_ADDRESS (1U << 9) +#define PAL_BUFFER_USAGE_INDIRECT (1U << 10) + +#define PAL_DEBUG_MESSAGE_SEVERITY_INFO 0 +#define PAL_DEBUG_MESSAGE_SEVERITY_WARNING 1 +#define PAL_DEBUG_MESSAGE_SEVERITY_ERROR 2 +#define PAL_DEBUG_MESSAGE_SEVERITY_COUNT 3 + +#define PAL_DEBUG_MESSAGE_TYPE_GENERAL 0 +#define PAL_DEBUG_MESSAGE_TYPE_VALIDATION 1 +#define PAL_DEBUG_MESSAGE_TYPE_PERFORMANCE 2 +#define PAL_DEBUG_MESSAGE_TYPE_COUNT 3 + +#define PAL_USAGE_STATE_UNDEFINED 0 +#define PAL_USAGE_STATE_PRESENT 1 +#define PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE 2 +#define PAL_USAGE_STATE_DEPTH_ATTACHMENT_READ 3 +#define PAL_USAGE_STATE_DEPTH_ATTACHMENT_WRITE 4 +#define PAL_USAGE_STATE_STENCIL_ATTACHMENT_READ 5 +#define PAL_USAGE_STATE_STENCIL_ATTACHMENT_WRITE 6 +#define PAL_USAGE_STATE_FRAGMENT_SHADING_RATE_ATTACHMENT_READ 7 +#define PAL_USAGE_STATE_TRANSFER_READ 8 +#define PAL_USAGE_STATE_TRANSFER_WRITE 9 +#define PAL_USAGE_STATE_VERTEX_READ 10 +#define PAL_USAGE_STATE_INDEX_READ 11 +#define PAL_USAGE_STATE_INDIRECT_READ 12 +#define PAL_USAGE_STATE_UNIFORM_READ 13 +#define PAL_USAGE_STATE_SHADER_READ 14 +#define PAL_USAGE_STATE_SHADER_WRITE 15 +#define PAL_USAGE_STATE_STORAGE_READ 16 +#define PAL_USAGE_STATE_STORAGE_WRITE 17 +#define PAL_USAGE_STATE_HOST_READ 18 +#define PAL_USAGE_STATE_HOST_WRITE 19 +#define PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ 20 +#define PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE 21 +#define PAL_USAGE_STATE_COUNT 22 + +#define PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER 0 +#define PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER 1 +#define PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE 2 +#define PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE 3 +#define PAL_DESCRIPTOR_TYPE_SAMPLER 4 +#define PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE 5 +#define PAL_DESCRIPTOR_TYPE_COUNT 6 + +#define PAL_DESCRIPTOR_INDEXING_FLAG_NONE 0 +#define PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND (1U << 0) +#define PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND (1U << 1) +#define PAL_DESCRIPTOR_INDEXING_FLAG_NON_UNIFORM_INDEXING (1U << 2) + +#define PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_TRACE (1U << 1) +#define PAL_ACCELERATION_STRUCTURE_BUILD_HINT_LOW_MEMORY (1U << 2) + +#define PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL 0 +#define PAL_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT 1 +#define PAL_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT 2 +#define PAL_RAY_TRACING_SHADER_GROUP_TYPE_COUNT 3 + +#define PAL_BUFFER_MEMORY_USAGE_MANUAL 0 +#define PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY 1 +#define PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD 2 +#define PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK 3 +#define PAL_BUFFER_MEMORY_USAGE_COUNT 4 + +#define PAL_IMAGE_MEMORY_USAGE_MANUAL 0 +#define PAL_IMAGE_MEMORY_USAGE_AUTO_GPU_ONLY 1 +#define PAL_IMAGE_MEMORY_USAGE_COUNT 2 + +#define PAL_RENDERING_FLAG_NONE 0 +#define PAL_RENDERING_FLAG_SUSPENDING (1U << 0) +#define PAL_RENDERING_FLAG_RESUMING (1U << 1) + +#define PAL_PIPELINE_STAGE_NONE 0 +#define PAL_PIPELINE_STAGE_VERTEX_SHADER (1U << 1) +#define PAL_PIPELINE_STAGE_FRAGMENT_SHADER (1U << 2) +#define PAL_PIPELINE_STAGE_COMPUTE_SHADER (1U << 3) +#define PAL_PIPELINE_STAGE_GEOMETRY_SHADER (1U << 4) +#define PAL_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER (1U << 5) +#define PAL_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER (1U << 6) +#define PAL_PIPELINE_STAGE_RAY_TRACING_SHADER (1U << 7) +#define PAL_PIPELINE_STAGE_TASK_SHADER (1U << 8) +#define PAL_PIPELINE_STAGE_MESH_SHADER (1U << 9) +#define PAL_PIPELINE_STAGE_VERTEX_INPUT (1U << 10) +#define PAL_PIPELINE_STAGE_INDEX_INPUT (1U << 11) +#define PAL_PIPELINE_STAGE_EARLY_DEPTH_STENCIL (1U << 12) +#define PAL_PIPELINE_STAGE_LATE_DEPTH_STENCIL (1U << 13) +#define PAL_PIPELINE_STAGE_TRANSFER (1U << 14) +#define PAL_PIPELINE_STAGE_HOST (1U << 15) +#define PAL_PIPELINE_STAGE_COLOR_ATTACHMENT (1U << 16) +#define PAL_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT (1U << 17) +#define PAL_PIPELINE_STAGE_INDIRECT_INPUT (1U << 18) +#define PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD (1U << 19) + +/** + * Required implementations: + * - enumerateAdapters + * - getAdapterInfo + * - getAdapterCapabilities + * - getAdapterFeatures + * - getHighestSupportedShaderTarget + * - createDevice + * - destroyDevice + * - getDeviceLostReason + * - allocateMemory + * - freeMemory + * - createQueue + * - destroyQueue + * - waitQueue + * - canQueuePresent + * - enumerateFormats + * - isFormatSupported + * - queryFormatImageUsages + * - queryFormatSampleCount + * - createImage + * - destroyImage + * - getImageInfo + * - getImageMemoryRequirements + * - bindImageMemory + * - createImageView + * - destroyImageView + * - createSampler + * - destroySampler + * - createShader + * - destroyShader + * - createFence + * - destroyFence + * - waitFence + * - isFenceSignaled + * - createSemaphore + * - destroySemaphore + * - createCommandPool + * - destroyCommandPool + * - allocateCommandBuffer + * - freeCommandBuffer + * - submitCommandBuffer + * - resetCommandBuffer + * - cmdBegin + * - cmdEnd + * - cmdExecuteCommandBuffer + * - cmdBeginRendering + * - cmdEndRendering + * - cmdCopyBuffer + * - cmdCopyBufferToImage + * - cmdCopyImage + * - cmdCopyImageToBuffer + * - cmdBindPipeline + * - cmdSetViewport + * - cmdSetScissors + * - cmdBindVertexBuffers + * - cmdBindIndexBuffer + * - cmdDraw + * - cmdDrawIndexed + * - cmdImageBarrier + * - cmdBufferBarrier + * - cmdDispatch + * - cmdBindDescriptorSet + * - cmdPushConstants + * - createBuffer + * - destroyBuffer + * - getBufferMemoryRequirements + * - computeImageStagingRequirements + * - writeImageStaging + * - bindBufferMemory + * - mapBuffer + * - unmapBuffer + * - createDescriptorSetLayout + * - destroyDescriptorSetLayout + * - createDescriptorPool + * - destroyDescriptorPool + * - resetDescriptorPool + * - allocateDescriptorSet + * - updateDescriptorSet + * - createPipelineLayout + * - destroyPipelineLayout + * - createGraphicsPipeline + * - createComputePipeline + * - destroyPipeline + */ +#define PAL_GRAPHICS_BACKEND_VTABLE_VERSION_1 0 + +/** + * @struct PalAdapter + * @brief Opaque handle to an adapter (GPU). + * + * @since 2.0 + */ +typedef struct PalAdapter PalAdapter; + +/** + * @struct PalDevice + * @brief Opaque handle to a device. Devices are created from an adapter (GPU). + * + * @since 2.0 + */ +typedef struct PalDevice PalDevice; + +/** + * @struct PalMemory + * @brief Opaque handle to a device memory. This is not CPU memory. + * + * @since 2.0 + */ +typedef struct PalMemory PalMemory; + +/** + * @struct PalQueue + * @brief Opaque handle to a queue. + * + * @since 2.0 + */ +typedef struct PalQueue PalQueue; + +/** + * @struct PalSurface + * @brief Opaque handle to a surface. + * + * @since 2.0 + */ +typedef struct PalSurface PalSurface; + +/** + * @struct PalSwapchain + * @brief Opaque handle to a swapchain. + * + * @since 2.0 + */ +typedef struct PalSwapchain PalSwapchain; + +/** + * @struct PalImage + * @brief Opaque handle to an image. + * + * @since 2.0 + */ +typedef struct PalImage PalImage; + +/** + * @struct PalImageView + * @brief Opaque handle to an image view. + * + * @since 2.0 + */ +typedef struct PalImageView PalImageView; + +/** + * @struct PalShader + * @brief Opaque handle to a shader. + * + * @since 2.0 + */ +typedef struct PalShader PalShader; + +/** + * @struct PalBuffer + * @brief Opaque handle to a buffer. + * + * @since 2.0 + */ +typedef struct PalBuffer PalBuffer; + +/** + * @struct PalFence + * @brief Opaque handle to a fence. + * + * @since 2.0 + */ +typedef struct PalFence PalFence; + +/** + * @struct PalSemaphore + * @brief Opaque handle to a semaphore. + * + * @since 2.0 + */ +typedef struct PalSemaphore PalSemaphore; + +/** + * @struct PalCommandPool + * @brief Opaque handle to a command pool. + * + * @since 2.0 + */ +typedef struct PalCommandPool PalCommandPool; + +/** + * @struct PalCommandBuffer + * @brief Opaque handle to a command buffer. + * + * @since 2.0 + */ +typedef struct PalCommandBuffer PalCommandBuffer; + +/** + * @struct PalDescriptorSetLayout + * @brief Opaque handle to a descriptor set layout. + * + * This defines the layout, ordering and the number of descriptors a descriptor set uses. + * + * The layouts should reflect the exact layout of the shaders. Eg. + * descriptorBindings[2] = { sampler, sampled image } is different from + * descriptorBindings[2] = { sampled image, sampler }. The ordering must be correct. + * + * @since 2.0 + */ +typedef struct PalDescriptorSetLayout PalDescriptorSetLayout; + +/** + * @struct PalDescriptorPool + * @brief Opaque handle to a descriptor pool. + * + * @since 2.0 + */ +typedef struct PalDescriptorPool PalDescriptorPool; + +/** + * @struct PalDescriptorSet + * @brief Opaque handle to a descriptor set. + * + * @since 2.0 + */ +typedef struct PalDescriptorSet PalDescriptorSet; + +/** + * @struct PalSampler + * @brief Opaque handle to a sampler. + * + * @since 2.0 + */ +typedef struct PalSampler PalSampler; + +/** + * @struct PalPipelineLayout + * @brief Opaque handle to a pipeline layout. + * + * @since 2.0 + */ +typedef struct PalPipelineLayout PalPipelineLayout; + +/** + * @struct PalPipeline + * @brief Opaque handle to a pipeline. This is the same handle used for all pipeline types + * (Graphics, Compute and Ray tracing). + * + * @since 2.0 + */ +typedef struct PalPipeline PalPipeline; + +/** + * @struct PalShaderBindingTable + * @brief Opaque handle to a shader binding table. + * + * @since 2.0 + */ +typedef struct PalShaderBindingTable PalShaderBindingTable; + +/** + * @struct PalAccelerationStructure + * @brief Opaque handle to an acceleration structure. + * + * @since 2.0 + */ +typedef struct PalAccelerationStructure PalAccelerationStructure; + +/** + * @typedef PalDebugMessageSeverity + * @brief Debugger messages severity types used to filter incoming messages. + * + * All message severities follow the format `PAL_DEBUG_MESSAGE_SEVERITY_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDebugMessageSeverity; + +/** + * @typedef PalDebugMessageType + * @brief Debugger messages types used to filter incoming messages. + * + * All message types follow the format `PAL_DEBUG_MESSAGE_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDebugMessageType; + +/** + * @typedef PalDeviceAddress + * @brief Adapter address. Used to get adapter (GPU) address of mostly buffers. + * + * @since 2.0 + */ +typedef uint64_t PalDeviceAddress; + +/** + * @typedef PalAdapterFeatures + * @brief Adapter features. This is a bitmask. + * + * All adapter features follow the format `PAL_ADAPTER_FEATURE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint64_t PalAdapterFeatures; + +/** + * @typedef PalAdapterType + * @brief Adapter (GPU) types. + * + * All adapter types follow the format `PAL_ADAPTER_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalAdapterType; + +/** + * @typedef PalAdapterApiType + * @brief Adapter API types. + * + * All adapter api types follow the format `PAL_ADAPTER_API_TYPE_**` for + * consistency and API use. + * + * Customs backends that dont fit the already declared api types should use + * `PAL_ADAPTER_API_TYPE_CUSTOM`. + * + * @since 2.0 + */ +typedef uint32_t PalAdapterApiType; + +/** + * @typedef PalQueueType + * @brief Queue types. + * + * All queue types follow the format `PAL_QUEUE_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalQueueType; + +/** + * @typedef PalPresentMode + * @brief Present modes + * + * All present modes follow the format `PAL_PRESENT_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPresentMode; + +/** + * @typedef PalCompositeAplha + * @brief Composite alphas + * + * All composite alphas follow the format `PAL_COMPOSITE_ALPHA_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCompositeAplha; + +/** + * @typedef PalFormat + * @brief Format types. + * + * All format types follow the format `PAL_FORMAT_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFormat; + +/** + * @typedef PalImageUsages + * @brief Image usages. Multiple image usages can be OR'ed together using bitwise + * OR operator (`|`). + * + * All image usages follow the format `PAL_IMAGE_USAGE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalImageUsages; + +/** + * @typedef PalShaderFormats + * @brief Shader formats. This is a bitmask. + * + * All shader formats follow the format `PAL_SHADER_FORMAT_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalShaderFormats; + +/** + * @typedef PalLoadOp + * @brief Load operation type. + * + * All load operation type follow the format `PAL_LOAD_OP_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalLoadOp; + +/** + * @typedef PalStoreOp + * @brief Store operation type. + * + * All store operation type follow the format `PAL_STORE_OP_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalStoreOp; + +/** + * @typedef PalMemoryType + * @brief Memory types. + * + * All memory types follow the format `PAL_MEMORY_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalMemoryType; + +/** + * @typedef PalImageType + * @brief Image types. + * + * All image types follow the format `PAL_IMAGE_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalImageType; + +/** + * @typedef PalImageAspect + * @brief Image aspects. + * + * All image aspect follow the format `PAL_IMAGE_ASPECT_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalImageAspect; + +/** + * @typedef PalImageViewType + * @brief Image view types. + * + * All image view types follow the format `PAL_IMAGE_VIEW_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalImageViewType; + +/** + * @typedef PalFilterMode + * @brief Filter modes. + * + * All filter modes follow the format `PAL_FILTER_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFilterMode; + +/** + * @typedef PalSamplerMipmapMode + * @brief Sampler mipmap modes. + * + * All sampler mipmap modes follow the format `PAL_SAMPLER_MIPMAP_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalSamplerMipmapMode; + +/** + * @typedef PalSamplerAddressMode + * @brief Sampler address modes. + * + * All sampler address modes follow the format `PAL_SAMPLER_ADDRESS_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalSamplerAddressMode; + +/** + * @typedef PalBorderColor + * @brief Border color. + * + * All border colors follow the format `PAL_BORDER_COLOR_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalBorderColor; + +/** + * @typedef PalSurfaceFormat + * @brief Surface format types. + * + * All surface format types follow the format `PAL_SURFACE_FORMAT_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalSurfaceFormat; + +/** + * @typedef WindowInstanceType + * @brief Display types for a window. + * + * All window display types follow the format `PAL_WINDOW_INSTANCE_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalWindowInstanceType; + +/** + * @typedef PalShaderStage + * @brief shader stage types. + * + * All shader stage types follow the format `PAL_SHADER_STAGE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalShaderStage; + +/** + * @typedef PalSampleCount + * @brief sample count. + * + * All sample count follow the format `PAL_SAMPLE_COUNT_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalSampleCount; + +/** + * @typedef PalPrimitiveTopology + * @brief Primitve topology types. + * + * All primitve topology types follow the format `PAL_PRIMITIVE_TOPOLOGY_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPrimitiveTopology; + +/** + * @typedef PalCullMode + * @brief Cull modes. + * + * All cull modes follow the format `PAL_CULL_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCullMode; + +/** + * @typedef PalFrontFace + * @brief Front face modes. + * + * All front face modes follow the format `PAL_FRONT_FACE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFrontFace; + +/** + * @typedef PalPolygonMode + * @brief Polygon modes. + * + * All polygon modes follow the format `PAL_POLYGON_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPolygonMode; + +/** + * @typedef PalStencilFaceFlags + * @brief Stencil face flags. Multiple stencil face flags can be OR'ed together using bitwise + * OR operator (`|`). + * + * All tencil face flags follow the format `PAL_STENCIL_FACE_FLAG_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalStencilFaceFlags; + +/** + * @typedef PalVertexType + * @brief Vertex attribute types. + * + * All vertex attribute types follow the format `PAL_VERTEX_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalVertexType; + +/** + * @typedef PalVertexSemanticID + * @brief Vertex semantic id types. + * + * All vertex semantic id types follow the format `PAL_VERTEX_SEMANTIC_ID_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalVertexSemanticID; + +/** + * @typedef PalCommandBufferType + * @brief Command buffer types. + * + * All command buffer types follow the format `PAL_COMMAND_BUFFER_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCommandBufferType; + +/** + * @typedef PalVertexLayoutType + * @brief Vertex layout types. + * + * All vertex layout types follow the format `PAL_VERTEX_LAYOUT_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalVertexLayoutType; + +/** + * @typedef PalCompareOp + * @brief Compare operation modes. + * + * All compare operation modes follow the format `PAL_COMPARE_OP_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCompareOp; + +/** + * @typedef PalStencilOp + * @brief Stencil operation modes. + * + * All stencil operation modes follow the format `PAL_STENCIL_OP_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalStencilOp; + +/** + * @typedef PalBlendOp + * @brief Blend operation modes. + * + * All blend operation modes follow the format `PAL_BLEND_OP_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalBlendOp; + +/** + * @typedef PalBlendFactor + * @brief Blend factor modes. + * + * All blend factor modes follow the format `PAL_BLEND_FACTOR_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalBlendFactor; + +/** + * @typedef PalColorMask + * @brief Color mask flags. Multiple color mask flags can be OR'ed together using bitwise + * OR operator (`|`). + * + * `PAL_COLOR_MASK_NONE` is not a bit and must not be combined with other bits. + * + * All color mask flags follow the format `PAL_COLOR_MASK_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalColorMask; + +/** + * @typedef PalResolveMode + * @brief Resolve modes. + * + * All resolve modes follow the format `PAL_RESOLVE_MODE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalResolveMode; + +/** + * @typedef PalFragmentShadingRate + * @brief Fragment shading rates. + * + * All fragment shading rates follow the format `PAL_FRAGMENT_SHADING_RATE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFragmentShadingRate; + +/** + * @typedef PalFragmentShadingRateCombinerOp + * @brief Fragment shading rate combiner operaton modes. + * + * All fragment shading rate combiner operation modes follow the format + * `PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFragmentShadingRateCombinerOp; + +/** + * @typedef PalAccelerationStructureType + * @brief Acceleration structure types. + * + * All acceleration structure types follow the format `PAL_ACCELERATION_STRUCTURE_TYPE_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalAccelerationStructureType; + +/** + * @typedef PalAccelerationStructureBuildMode + * @brief Acceleration structure build modes. + * + * All acceleration structure build modes follow the format + * `PAL_ACCELERATION_STRUCTURE_BUILD_MODE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalAccelerationStructureBuildMode; + +/** + * @typedef PalAccelerationStructureBuildHints + * @brief Acceleration structure build hints. Multiple hints can be OR'ed together using + * bitwise OR operator (`|`). Hints can be ignored by the driver. + * + * All acceleration structure build hints follow the format + * `PAL_ACCELERATION_STRUCTURE_BUILD_HINT_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalAccelerationStructureBuildHints; + +/** + * @typedef PalAccelerationStructureInstanceFlags + * @brief Acceleration structure instance flags. Multiple flags can be OR'ed together using + * bitwise OR operator (`|`). + * + * All acceleration structure instance flags follow the format + * `PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalAccelerationStructureInstanceFlags; + +/** + * @typedef PalGeometryType + * @brief Geometry types. + * + * All geometry types follow the format `PAL_GEOMETRY_TYPE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGeometryType; + +/** + * @typedef PalGeometryFlags + * @brief Geometry flags. Multiple flags can be OR'ed together using + * bitwise OR operator (`|`). Not all combinations are valid. + * + * All geometry flags follow the format `PAL_GEOMETRY_FLAG_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGeometryFlags; + +/** + * @typedef PalIndexType + * @brief Index types. + * + * All index types follow the format `PAL_INDEX_TYPE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalIndexType; + +/** + * @typedef PalBufferUsages + * @brief Buffer usages. Multiple buffer usages can be OR'ed together using bitwise + * OR operator (`|`). + * + * All buffer usages follow the format `PAL_BUFFER_USAGE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalBufferUsages; + +/** + * @typedef PalUsageState + * @brief Usage states. + * + * All usage states follow the format `PAL_USAGE_STATE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalUsageState; + +/** + * @typedef PalDescriptorType + * @brief Descriptor types. + * + * All descriptor types follow the format `PAL_DESCRIPTOR_TYPE_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDescriptorType; + +/** + * @typedef PalRayTracingShaderGroupType + * @brief Ray tracing shader group types. + * + * All ray tracing shader group types follow the format `PAL_RAY_TRACING_SHADER_GROUP_TYPE_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalRayTracingShaderGroupType; + +/** + * @typedef PalDescriptorIndexingFlags + * @brief Descriptor indexing subfeature flags. + * + * These flags show the capabilities of the descriptor indexing feature. Each flag determines + * the operations that are allowed. + * + * `PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND`: Descriptors in a descriptor set can be updated + * after the descriptor set been bound in a command buffer. + * + * `PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND`: Unused descriptors can be left uninitialized if + * a shader never accesses them. + * + * `PAL_DESCRIPTOR_INDEXING_FLAG_NON_UNIFORM_INDEXING`: Different threads can access different + * descriptors. + * + * All descriptor indexing flags follow the format `PAL_DESCRIPTOR_INDEXING_FLAG_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalDescriptorIndexingFlags; + +/** + * @typedef PalBufferMemoryUsage + * @brief Buffer memory usages. + * + * `PAL_BUFFER_MEMORY_USAGE_MANUAL`: PAL does not allocate memory for the buffer. Users are required + * to get the required size and allocate memory for the buffer after the buffer has been created. + * The lifetime of the memory is the responsibility of the user. + * + * `PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY`: PAL allocates gpu only memory and manages the memory + * for the user. This is ideal if a custom allocator will not be used by the user. + * + * `PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD`: PAL allocates cpu upload memory and manages the + * memory for the user. This is ideal if a custom allocator will not be used by the user. + * + * `PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK`: PAL allocates cpu readback memory and manages the + * memory for the user. This is ideal if a custom allocator will not be used by the user. + * + * All buffer memory usages follow the format `PAL_BUFFER_MEMORY_USAGE_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalBufferMemoryUsage; + +/** + * @typedef PalImageMemoryUsage + * @brief Image memory usages. + * + * `PAL_IMAGE_MEMORY_USAGE_MANUAL`: PAL does not allocate memory for the image. Users are required + * to get the required size and allocate memory for the image after the image has been created. + * The lifetime of the memory is the responsibility of the user. + * + * `PAL_IMAGE_MEMORY_USAGE_AUTO_GPU_ONLY`: PAL allocates gpu only memory and manages the memory + * for the user. This is ideal if a custom allocator will not be used by the user. + * + * All image memory usages follow the format `PAL_IMAGE_MEMORY_USAGE_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalImageMemoryUsage; + +/** + * @typedef PalRenderingFlags + * @brief Rendering flags. + * + * All rendering flags follow the format `PAL_RENDERING_FLAG_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalRenderingFlags; + +/** + * @typedef PalPipelineStages + * @brief Pipeline stages. Multiple pipeline usages can be OR'ed together using bitwise + * OR operator (`|`). + * + * All pipeline stages follow the format `PAL_PIPELINE_STAGE_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPipelineStages; + +/** + * @typedef PalGraphicsBackendVtableVersion + * @brief Graphics backend vtable versions. + * + * All graphics backend vtable versions follow the format `PAL_GRAPHICS_BACKEND_VTABLE_VERSION_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGraphicsBackendVtableVersion; + +/** + * @typedef PalDebugCallback + * @brief Function pointer type used for debug callbacks. + * + * @param userData Optional pointer to user data passed from ::PalGraphicsDebugger. Can be + * `nullptr`. + * @param severity Severity of the message. (`PAL_DEBUG_MESSAGE_SEVERITY_INFO`, + * `PAL_DEBUG_MESSAGE_SEVERITY_WARNING` and `PAL_DEBUG_MESSAGE_SEVERITY_ERROR`). + * @param type Type of the message. (`PAL_DEBUG_MESSAGE_TYPE_GENERAL`, + * `PAL_DEBUG_MESSAGE_TYPE_VALIDATION` and `PAL_DEBUG_MESSAGE_TYPE_PERFORMANCE`). + * @param msg Null-terminated UTF-8 debug message. + * + * @since 2.0 + * @sa palInitGraphics + */ +typedef void(PAL_CALL* PalDebugCallback)( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg); + +/** + * @struct PalAdapterInfo + * @brief Information about an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint64_t vram; /**< Total video memory in bytes*/ + uint64_t sharedMemory; /**< Total shared memory in bytes*/ + uint64_t driverVersion; /**< Adapter version.*/ + uint32_t vendorId; /**< Adapter vendor id.*/ + uint32_t deviceId; /**< Adapter device id.*/ + PalShaderFormats shaderFormats; /**< (eg. `PAL_SHADER_FORMAT_SPIRV`)*/ + PalAdapterType type; /**< (eg. `PAL_ADAPTER_TYPE_DISCRETE`).*/ + PalAdapterApiType apiType; /**< (eg. `PAL_ADAPTER_API_TYPE_VULKAN`).*/ + char name[PAL_ADAPTER_NAME_SIZE]; /**< Adapter name.*/ + char backendName[PAL_ADAPTER_BACKEND_NAME_SIZE]; /**< Adapter backend name.*/ + uint32_t reserved; /**< 0 for now.*/ +} PalAdapterInfo; + +/** + * @struct PalImageCapabilities + * @brief Image capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxWidth; /**< Max width in pixels.*/ + uint32_t maxHeight; /**< Max height in pixels.*/ + uint32_t maxDepth; /**< Max depth in pixels.*/ + uint32_t maxArrayLayers; /**< Max array layers.*/ + uint32_t maxMipLevels; /**< Max mipmap levels.*/ +} PalImageCapabilities; + +/** + * @struct PalResourceCapabilities + * @brief Resource capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxPerStageSampledImages; /**< Max sampled images per shader stage .*/ + uint32_t maxPerSetSampledImages; /**< Max sampled images per descriptor set.*/ + uint32_t maxPerStageStorageImages; /**< Max storage images per shader stage.*/ + uint32_t maxPerSetStorageImages; /**< Max storage images per descriptor set.*/ + uint32_t maxPerStageSamplers; /**< Max samplers per shader stage.*/ + uint32_t maxPerSetSamplers; /**< Max samplers per descriptor set.*/ + uint32_t maxPerStageStorageBuffers; /**< Max storage buffers per shader stage.*/ + uint32_t maxPerSetStorageBuffers; /**< Max storage buffers per descriptor set.*/ + uint32_t maxPerStageUniformBuffers; /**< Max uniform buffers per shader stage.*/ + uint32_t maxPerSetUniformBuffers; /**< Max uniform buffers per descriptor set.*/ + uint32_t maxPerStageAccelerationStructure; /**< Max acceleration structures per shader stage.*/ + uint32_t maxPerSetAccelerationStructure; /**< Max acceleration structures per descriptor set.*/ + uint32_t maxBoundSets; /**< Max bound descriptor sets.*/ +} PalResourceCapabilities; + +/** + * @struct PalComputeCapabilities + * @brief Compute capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxWorkGroupInvocations; /**< Max invocations across all workgroups.*/ + uint32_t maxWorkGroupCount[3]; /**< Max workgroups per dimension.*/ + uint32_t maxWorkGroupSize[3]; /**< Max workgroup size per dimension.*/ +} PalComputeCapabilities; + +/** + * @struct PalViewportCapabilities + * @brief Viewport capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxWidth; /**< Max width in pixels.*/ + uint32_t maxHeight; /**< Max height in pixels.*/ + float minBoundsRange; /**< Min coordinate range.*/ + float maxBoundsRange; /**< Max coordinate range.*/ +} PalViewportCapabilities; + +/** + * @struct PalAdapterCapabilities + * @brief Capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxComputeQueues; /**< Max compute queues that can be created.*/ + uint32_t maxGraphicsQueues; /**< Max graphics queues that can be created.*/ + uint32_t maxCopyQueues; /**< Max copy queues that can be created.*/ + uint32_t maxColorAttachments; /**< Max number of simultaneous color attachments.*/ + uint32_t maxUniformBufferSize; /**< Max uniform buffer size in bytes.*/ + uint32_t maxStorageBufferSize; /**< Max storage buffer size in bytes.*/ + uint32_t maxPushConstantSize; /**< Max push constants size in bytes.*/ + uint32_t maxVertexLayouts; /**< Max vertex layouts.*/ + uint32_t maxVertexAttributes; /**< Max vertex attributes across all vertex layouts.*/ + uint32_t maxTessellationPatchPoint; /**< Max tessellation patch point.*/ + PalViewportCapabilities viewportCaps; /**< Viewport capabilities.*/ + PalImageCapabilities imageCaps; /**< Image capabilities.*/ + PalResourceCapabilities resourceCaps; /**< resource (descriptors) capabilities.*/ + PalComputeCapabilities computeCaps; /**< Compute capabilities.*/ +} PalAdapterCapabilities; + +/** + * @struct PalSamplerAnisotropyCapabilities + * @brief Sampler anisotropy capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxAnisotropy; /**< Max texture filtering level.*/ +} PalSamplerAnisotropyCapabilities; + +/** + * @struct PalMultiViewCapabilities + * @brief Multi view capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxViewCount; /**< Max number views of an image.*/ +} PalMultiViewCapabilities; + +/** + * @struct PalMultiViewportCapabilities + * @brief Multi viewport capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxCount; /**< Max number of simultaneous viewports.*/ +} PalMultiViewportCapabilities; + +/** + * @struct PalDepthStencilCapabilities + * @brief Depth stencil capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t supportedDepthResolveModes; /**< Masks of supported depth resolve modes.*/ + uint32_t supportedStencilResolveModes; /**< Masks of supported stencil resolve modes.*/ + + /** If `PAL_TRUE`, depth and stencil can have seperate resolve modes.*/ + PalBool supportsIndependentResolve; + + /**If `PAL_TRUE`, depth/stencil can be `PAL_RESOLVE_MODE_NONE` while the other is resolved.*/ + PalBool supportsIndependentResolveNone; +} PalDepthStencilCapabilities; + +/** + * @struct PalFragmentShadingRateCapabilities + * @brief Fragment shading rate capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t supportedShadingRates; /**< Masks of supported shading rates.*/ + uint32_t supportedCombinerOps; /**< Masks of supported combiner operations.*/ + uint32_t minTexelWidth; /**< Min texel width in pixels*/ + uint32_t minTexelHeight; /**< Min texel height in pixels*/ + uint32_t maxTexelWidth; /**< Max texel width in pixels*/ + uint32_t maxTexelHeight; /**< Max texel height in pixels*/ +} PalFragmentShadingRateCapabilities; + +/** + * @struct PalMeshShaderCapabilities + * @brief Mesh shader capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxOutputPrimitives; /**< Max number of primitives per mesh workgroup.*/ + uint32_t maxOutputVertices; /**< Max number of vertices per mesh workgroup.*/ + uint32_t maxWorkGroupInvocations; /**< Max mesh invocations across all workgroups.*/ + uint32_t maxTaskWorkGroupInvocations; /**< Max task invocations across all workgroups.*/ + uint32_t maxWorkGroupCount[3]; /**< Max mesh workgroups per dimension.*/ + uint32_t maxTaskWorkGroupCount[3]; /**< Max task workgroups per dimension.*/ +} PalMeshShaderCapabilities; + +/** + * @struct PalRayTracingCapabilities + * @brief Ray tracing capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t maxRecursionDepth; /**< Max number of ray recursion.*/ + uint32_t maxHitAttributeSize; /**< Max attributes size in bytes.*/ + uint32_t maxInstanceCount; /**< Max number of instances.*/ + uint32_t maxPrimitiveCount; /**< Max number of primitives.*/ + uint32_t maxGeometryCount; /**< Max number of geometries.*/ + uint32_t maxPayloadSize; /**< Max payload size in bytes.*/ + uint32_t maxDispatchInvocations; /**< Max number of dispatch threads.*/ +} PalRayTracingCapabilities; + +/** + * @struct PalDescriptorIndexingCapabilities + * @brief Descriptor indexing capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + PalDescriptorIndexingFlags flags; /**< Capabilities flags. see `PalDescriptorIndexingFlags`*/ + uint32_t maxPerStageSampledImages; /**< Max sampled images per shader stage .*/ + uint32_t maxPerSetSampledImages; /**< Max sampled images per descriptor set.*/ + uint32_t maxPerStageStorageImages; /**< Max storage images per shader stage.*/ + uint32_t maxPerSetStorageImages; /**< Max storage images per descriptor set.*/ + uint32_t maxPerStageSamplers; /**< Max samplers per shader stage.*/ + uint32_t maxPerSetSamplers; /**< Max samplers per descriptor set.*/ + uint32_t maxPerStageStorageBuffers; /**< Max storage buffers per shader stage.*/ + uint32_t maxPerSetStorageBuffers; /**< Max storage buffers per descriptor set.*/ + uint32_t maxPerStageUniformBuffers; /**< Max uniform buffers per shader stage.*/ + uint32_t maxPerSetUniformBuffers; /**< Max uniform buffers per descriptor set.*/ + uint32_t maxPerStageAccelerationStructure; /**< Max acceleration structures per shader stage.*/ + uint32_t maxPerSetAccelerationStructure; /**< Max acceleration structures per descriptor set.*/ +} PalDescriptorIndexingCapabilities; + +/** + * @struct PalSurfaceCapabilities + * @brief surface capabilities of an adapter (GPU). + * + * @since 2.0 + */ +typedef struct { + uint32_t supportedPresentModes; /**< Masks of supported present modes.*/ + uint32_t supportedCompositeAlphas; /**< Masks of supported composite alphas.*/ + uint32_t supportedFormats; /**< Masks of supported surface formats.*/ + uint32_t minImageCount; /**< Min image or back buffer count.*/ + uint32_t maxImageCount; /**< Max image or back buffer count.*/ + uint32_t minImageWidth; /**< Min image width in pixels.*/ + uint32_t minImageHeight; /**< Min image height in pixels.*/ + uint32_t maxImageWidth; /**< Max image width in pixels.*/ + uint32_t maxImageHeight; /**< Max image height in pixels.*/ + uint32_t maxImageArrayLayers; /**< Max number of image layers.*/ +} PalSurfaceCapabilities; + +/** + * @struct PalFormatInfo + * @brief Information about a format. This includes the supported image usages and maximum sample + * count from the provided format. + * + * @since 2.0 + */ +typedef struct { + PalImageUsages usages; /**< (eg. `PAL_IMAGE_USAGE_COLOR`).*/ + PalFormat format; /**< (eg. `PAL_FORMAT_R8G8B8A8_UNORM`).*/ + PalSampleCount sampleCount; /**< (eg. `PAL_SAMPLE_COUNT_8`).*/ +} PalFormatInfo; + +/** + * @struct PalImageInfo + * @brief Information about an image. + * + * @since 2.0 + */ +typedef struct { + PalImageUsages usages; /**< (eg. `PAL_IMAGE_USAGE_COLOR`).*/ + uint32_t width; /**< Width of the image in pixels.*/ + uint32_t height; /**< Height of the image in pixels.*/ + uint32_t depth; /**< Depth of the image in pixels.*/ + uint32_t arrayLayerCount; /**< Number of array layers.*/ + uint32_t mipLevelCount; /**< Number of mipmap levels.*/ + PalSampleCount sampleCount; /**< (eg. `PAL_SAMPLE_COUNT_8`).*/ + PalImageType type; /**< (eg. `PAL_IMAGE_TYPE_2D`).*/ + PalFormat format; /**< (eg. `PAL_FORMAT_R8G8B8A8_UNORM`).*/ + PalBool belongsToSwapchain; /**< If `PAL_TRUE`, the image belongs to a swapchain.*/ +} PalImageInfo; + +/** + * @struct PalClearValue + * @brief Clear values used with rendering. + * + * If used with a color attachment, the color values will be used and depth and stencil + * will be used with depth stencil attachments. + * + * @since 2.0 + */ +typedef struct { + float color[4]; /**< Color clear value.*/ + float depth; /**< Depth clear value.*/ + uint32_t stencil; /**< Stencil clear value.*/ +} PalClearValue; + +/** + * @struct PalAttachmentDesc + * @brief An attachment description. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalImageView* imageView; /**< Image view.*/ + PalImageView* resolveImageView; /**< Resolve image view. Can be `nullptr`.*/ + PalLoadOp loadOp; /**< (eg. `PAL_LOAD_OP_CLEAR`).*/ + PalStoreOp storeOp; /**< (eg. `PAL_STORE_OP_STORE`).*/ + PalLoadOp stencilLoadOp; /**< (eg. `PAL_LOAD_OP_DONT_CARE`).*/ + PalStoreOp stencilStoreOp; /**< (eg. `PAL_STORE_OP_DONT_CARE`).*/ + PalResolveMode resolveMode; /**< Used if resolveImageView is set.*/ + PalResolveMode stencilResolveMode; /**< Used if resolveImageView is set.*/ + PalClearValue clearValue; /**< Clear value for color and depth/stencil attachments.*/ +} PalAttachmentDesc; + +/** + * @struct PalViewport + * @brief A viewport in pixels (float). + * + * @since 2.0 + */ +typedef struct { + float x; /**< X position in pixels.*/ + float y; /**< Y position in pixels.*/ + float width; /**< Width in pixels.*/ + float height; /**< Height in pixels.*/ + float minDepth; /**< Min depth value.*/ + float maxDepth; /**< Max depth value.*/ +} PalViewport; + +/** + * @struct PalRect2D + * @brief A 2D rectangle in pixels. + * + * @since 2.0 + */ +typedef struct { + int32_t x; /**< X position in pixels.*/ + int32_t y; /**< Y position in pixels.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ +} PalRect2D; + +/** + * @struct PalMemoryRequirements + * @brief Memory requirements for a resource (image, buffer etc). + * + * @since 2.0 + */ +typedef struct { + uint64_t size; /**< Required size in bytes.*/ + uint64_t alignment; /**< Required alignment in bytes.*/ + uint64_t memoryMask; /**< Memory masks used in allocations. Must not be changed.*/ + uint32_t supportedMemoryTypes; /**< Masks of supported memory types.*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalMemoryRequirements; + +/** + * @struct PalCommandBufferSubmitInfo + * @brief Submit information of a command buffer. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t waitValue; /**< Timeline semaphore value to wait on.*/ + uint64_t signalValue; /**< Timeline semaphore value to signal.*/ + PalCommandBuffer* cmdBuffer; /**< Command buffer to submit.*/ + PalSemaphore* waitSemaphore; /**< Wait semaphore.*/ + PalSemaphore* signalSemaphore; /**< Signal semaphore.*/ + PalFence* fence; /**< Fence to signal.*/ + PalPipelineStages waitStages; /**< (eg. `PAL_PIPELINE_STAGE_COLOR_ATTACHMENT`).*/ + PalPipelineStages signalStages; /**< (eg. `PAL_PIPELINE_STAGE_NONE`).*/ +} PalCommandBufferSubmitInfo; + +/** + * @struct PalSwapchainNextImageInfo + * @brief Next image information of a swapchain. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t timeout; /**< Timeout in milliseconds.*/ + PalSemaphore* signalSemaphore; /**< Timeline semaphore value to signal.*/ + PalFence* fence; /**< Fence to signal.*/ +} PalSwapchainNextImageInfo; + +/** + * @struct PalRenderingInfo + * @brief Information about how rendering should be done in graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalAttachmentDesc* colorAttachments; /**< Color attachments.*/ + PalAttachmentDesc* depthStencilAttachment; /**< Depth/Stencil attachment.*/ + PalImageView* fragmentShadingRateImageView; /**< Fragment shading rate image view.*/ + PalRect2D renderArea; /**< Rendering area of the attachments.*/ + PalRenderingFlags flags; /**< (eg. `PAL_RENDERING_FLAG_NONE`).*/ + uint32_t fragmentShadingRateTexelWidth; /**< Texel width for fragment shading rate.*/ + uint32_t fragmentShadingRateTexelHeight; /**< Texel height for fragment shading rate.*/ + uint32_t viewCount; /**< View count. Set to 1 for default.*/ + uint32_t arrayLayerCount; /**< Number of array layers for rendering.*/ + uint32_t colorAttachentCount; /**< Number of color attachments.*/ +} PalRenderingInfo; + +/** + * @struct PalRenderingLayoutInfo + * @brief Information about a pre-existing PalRenderingInfo. + * This is used to reference an already existing PalRenderingInfo. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalFormat* colorAttachmentsFormat; /**< Color attachments formats.*/ + uint32_t colorAttachentCount; /**< Number of color attachment formats.*/ + uint32_t viewCount; /**< View count. Set to 1 for default.*/ + PalSampleCount sampleCount; /**< (eg. `PAL_SAMPLE_COUNT_4`).*/ + PalRenderingFlags flags; /**< (eg. `PAL_RENDERING_FLAG_NONE`).*/ + PalFormat depthStencilAttachmentFormat; /**< Depth/Stencil attachment format.*/ + PalFormat fragmentShadingRateAttachmentFormat; /**< Fragment shading rate attachment format.*/ +} PalRenderingLayoutInfo; + +/** + * @struct PalWorkGroupBuildData + * @brief Compute or Mesh(or Task) workgroup input data build helper. + * + * Uninitialized fields may result in undefined behavior. `workCount` can be specified in pixels, + * vertices etc.Eg. an image of 800 x 600 will be [0] = 800, [1] = 600 and [2] = 1. + * + * @since 2.0 + */ +typedef struct { + uint32_t workCount[3]; /**< Workload per dimension. (eg. Image (200, 200, 1)).*/ + uint32_t workGroupSize[3]; /**< Threads per workgroup per axis of the adapter (GPU).*/ + uint32_t workGroupCount[3]; /**< Workgroups per axis of the adapter (GPU).*/ +} PalWorkGroupBuildData; + +/** + * @struct PalWorkGroupInfo + * @brief Information about compute or mesh(or task) dispatch data or a dispatch tile. + * + * @since 2.0 + */ +typedef struct { + uint32_t workGroupBase[3]; /**< Offset per dimension of a dispatch tile.*/ + uint32_t workGroupCount[3]; /**< Workgroup count per dimension of a dispatch tile.*/ +} PalWorkGroupInfo; + +/** + * @struct PalImageStagingRequirements + * @brief Requirements for an image staging buffer. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t bufferSize; /**< Required buffer size.*/ + uint32_t bufferRowLength; /**< Required buffer row length.*/ + uint32_t bufferImageHeight; /**< Required buffer image height.*/ +} PalImageStagingRequirements; + +/** + * @struct PalDrawIndirectData + * @brief Draw indirect data of a single draw call. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t vertexCount; /**< Vertex count.*/ + uint32_t instanceCount; /**< Instance count.*/ + uint32_t firstVertex; /**< First vertex.*/ + uint32_t firstInstance; /**< First instance.*/ +} PalDrawIndirectData; + +/** + * @struct PalDrawIndexedIndirectData + * @brief Draw indexed indirect data of a single draw call. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t indexCount; /**< Index count.*/ + uint32_t instanceCount; /**< Instance count.*/ + uint32_t firstIndex; /**< First index.*/ + int32_t vertexOffset; /**< Vertex offset.*/ + uint32_t firstInstance; /**< First instance.*/ +} PalDrawIndexedIndirectData; + +/** + * @struct PalDispatchIndirectData + * @brief Draw or dispatch indirect data of a single dispatch call. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t groupCountXOrWidth; /**< Number of groups on the x dimension or dispatch width.*/ + uint32_t groupCountXOrHeight; /**< Number of groups on the y dimension or dispatch height.*/ + uint32_t groupCountXOrDepth; /**< Number of groups on the z dimension or dispatch depth.*/ +} PalDispatchIndirectData; + +/** + * @struct PalVertexAttribute + * @brief Vertex attribute. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalVertexSemanticID semanticID; /**< (eg. `PAL_VERTEX_SEMANTIC_ID_POSITION`).*/ + PalVertexType type; /**< (eg. `PAL_VERTEX_TYPE_FLOAT3`).*/ +} PalVertexAttribute; + +/** + * @struct PalVertexLayout + * @brief Vertex layout. + * + * This defines the layout, ordering and the number of vertex attributes the layout uses. + * + * The layouts should reflect the exact layout of the shaders. Eg. + * attributes[2] = { position, color } is different from + * attributes[2] = { color, position }. The ordering must be correct. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalVertexAttribute* attributes; /**< Vertex attributes.*/ + uint32_t attributeCount; /**< Number of vertex attributes.*/ + PalVertexLayoutType type; /**< (eg. `PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX`).*/ + uint32_t binding; /**< Vertex buffer binding slot.*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalVertexLayout; + +/** + * @struct PalGraphicsDebugger + * @brief Graphics debugger. + * + * The debugger will not be initialized if PalGraphicsDebugger::callback is set and valid. + * + * @since 2.0 + */ +typedef struct { + void* userData; /**< Optional user provided data. Can be `nullptr`.*/ + PalDebugCallback callback; /**< Debug callback function.*/ + PalBool enableGPUValidation; /**< Enable GPU-Based validation.*/ + PalBool denyGeneral; /**< Do not recieve general messages.*/ + PalBool denyValidation; /**< Do not recieve validation messages.*/ + PalBool denyPerformance; /**< Do not recieve performance messages.*/ + PalBool denyInfoSeverity; /**< Do not recieve info severity messages.*/ + PalBool denyWarningSeverity; /**< Do not recieve warning severity messages.*/ + PalBool denyErrorSeverity; /**< Do not recieve error severity messages.*/ + uint32_t reserved; /**< 0 for now.*/ +} PalGraphicsDebugger; + +/** + * @struct PalRasterizerState + * @brief Rasterizer state. This is used with a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBool enableDepthClamp; /**< `PAL_TRUE` to enable depth clamp.*/ + PalBool enableDepthBias; /**< `PAL_TRUE` to enable depth bias.*/ + float depthBiasConstant; /**< Depth bias constant.*/ + float depthBiasSlope; /**< Depth bias slope.*/ + float depthBiasClamp; /**< Depth bias clamp.*/ + PalPolygonMode polygonMode; /**< (eg. `PAL_POLYGON_MODE_FILL`).*/ + PalCullMode cullMode; /**< (eg. `PAL_CULL_MODE_NONE`).*/ + PalFrontFace frontFace; /**< (eg. `PAL_FRONT_FACE_CLOCKWISE`).*/ +} PalRasterizerState; + +/** + * @struct PalMultisampleState + * @brief Multisample state. This is used with a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t sampleMask; /**< Set to 0 to use default.*/ + PalBool enableSampleShading; /**< `PAL_TRUE` to enable sample shading.*/ + PalBool enableAlphaToCoverage; /**< `PAL_TRUE` to enable alpha to coverage.*/ + PalSampleCount sampleCount; /**< (eg. `PAL_SAMPLE_COUNT_4`).*/ + float minSampleShading; /**< Minimum sample shading.*/ +} PalMultisampleState; + +/** + * @struct PalStencilOpState + * @brief Stencil operation state. This is used with a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalStencilOp failOp; /**< Stencil fail operation.*/ + PalStencilOp passOp; /**< Pass operation.*/ + PalStencilOp depthFailOp; /**< Depth fail operation.*/ + PalCompareOp compareOp; /**< Compare operation.*/ +} PalStencilOpState; + +/** + * @struct PalDepthStencilState + * @brief Depth stencil state. This is used with a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBool enableDepthTest; /**< `PAL_TRUE` to enable depth test.*/ + PalBool enableDepthWrite; /**< `PAL_TRUE` to enable depth write.*/ + PalBool enableStencilTest; /**< `PAL_TRUE` to enable stencil test.*/ + PalCompareOp compareOp; /**< Compare operation.*/ + PalStencilOpState frontStencilOpState; /**< Front stencil operation state.*/ + PalStencilOpState backStencilOpState; /**< Back stencil operation state.*/ +} PalDepthStencilState; + +/** + * @struct PalColorBlendAttachment + * @brief Color blend attachmeent. This is used with a graphics pipeline. + * + * Every rendering attachment (color, etc) must have a color blend attachment to + * describe how blending is applied to the attachment. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBool enableBlend; /**< `PAL_TRUE` to enable blending.*/ + PalColorMask colorWriteMask; /**< (eg. `PAL_COLOR_MASK_RED` | `PAL_COLOR_MASK_RED`).*/ + PalBlendFactor dstColorBlendFactor; /**< (eg. `PAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA`).*/ + PalBlendFactor srcColorBlendFactor; /**< (eg. `PAL_BLEND_FACTOR_SRC_ALPHA`).*/ + PalBlendOp colorBlendOp; /**< (eg. `PAL_BLEND_OP_ADD`).*/ + PalBlendFactor dstAlphaBlendFactor; /**< (eg. `PAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR`).*/ + PalBlendFactor srcAlphaBlendFactor; /**< (eg. `PAL_BLEND_FACTOR_SRC_COLOR`).*/ + PalBlendOp alphaBlendOp; /**< (eg. `PAL_BLEND_OP_SUBTRACT`).*/ +} PalColorBlendAttachment; + +/** + * @struct PalFragmentShadingRateState + * @brief Fragment shading rate state. This is used with a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalFragmentShadingRate rate; /**< (eg. `PAL_FRAGMENT_SHADING_RATE_2X2`).*/ + PalFragmentShadingRateCombinerOp combinerOps[2]; /**< Combiner operations.*/ +} PalFragmentShadingRateState; + +/** + * @struct PalAccelerationStructureInstance + * @brief Acceleration structure instance base data. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalAccelerationStructure* blas; /**< Bottom level acceleration structure.*/ + PalAccelerationStructureInstanceFlags flags; /**< Instance flags.*/ + uint32_t mask; /**< Only the lower 8-bits are used (0x00 - 0xFF).*/ + uint32_t instanceId; /**< User defined identifier.*/ + uint32_t hitGroupOffset; /**< Offset added to hitgroup index in the Shader Binding Table.*/ + float transform[12]; /**< Transform (row major 3x4).*/ +} PalAccelerationStructureInstance; + +/** + * @struct PalAccelerationStructureBuildSize + * @brief Acceleration structure build size. + * + * @since 2.0 + */ +typedef struct { + uint64_t accelerationStructureSize; /**< Required acceleration structure size in bytes.*/ + uint64_t scratchBufferSize; /**< Required scratch buffer size in bytes.*/ + uint64_t updateScratchBufferSize; /**< Required scratch buffer update size in bytes.*/ +} PalAccelerationStructureBuildSize; + +/** + * @struct PalGeometryDataTriangle + * @brief Acceleration structure triangle geometry data. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDeviceAddress vertexBufferAddress; /**< Address of the vertex buffer.*/ + PalDeviceAddress indexBufferAddress; /**< Address of the index buffer.*/ + PalDeviceAddress transformBufferAddress; /**< Address of the transform buffer.*/ + PalVertexType vertexType; /**< (eg. `PAL_VERTEX_TYPE_FLOAT3`)*/ + PalIndexType indexType; /**< (eg. `PAL_INDEX_TYPE_UINT32`).*/ + uint32_t vertexCount; /**< Number of vertices.*/ + uint32_t vertexStride; /**< Size of each vertex in bytes.*/ +} PalGeometryDataTriangle; + +/** + * @struct PalGeometryDataAABBS + * @brief Acceleration structure AABBS geometry data. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDeviceAddress bufferAddress; /**< Address of the AABBS buffer.*/ + uint64_t stride; /**< Size of each AABBS in bytes.*/ +} PalGeometryDataAABBS; + +/** + * @struct PalGeometry + * @brief Acceleration structure geometry. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const void* data; /**< This will be casted based on `type`.*/ + uint64_t primitiveCount; /**< Number of primitives in `data`.*/ + PalGeometryFlags flags; /**< (eg. `PAL_GEOMETRY_FLAG_OPAQUE`).*/ + PalGeometryType type; /**< (eg. `PAL_GEOMETRY_TYPE_TRIANGLE`).*/ +} PalGeometry; + +/** + * @struct PalAccelerationStructureBuildInfo + * @brief Build information of an acceleration structure. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalAccelerationStructure* dst; /**< Destination aceleration structure.*/ + PalAccelerationStructure* src; /**< Source aceleration structure. Used for updates.*/ + PalGeometry* geometries; /**< BLAS geometries. `nullptr` for TLAS*/ + PalDeviceAddress scratchBufferAddress; /**< Address of scratch buffer.*/ + PalDeviceAddress instanceBufferAddress; /**< Address of instance buffer. `nullptr` for BLAS.*/ + PalAccelerationStructureBuildHints buildHints; /**< Might be ignored by driver.*/ + PalAccelerationStructureType type; /**< (eg. `PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL`).*/ + PalAccelerationStructureBuildMode buildMode; /**< Build or update.*/ + uint32_t count; /**< Number of elements in `geometries` or `instanceBufferAddress`.*/ +} PalAccelerationStructureBuildInfo; + +/** + * @struct PalDescriptorSetLayoutBinding + * @brief Single descriptor set layout binding. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t descriptorCount; /**< Number of descriptors of `descriptorType`.*/ + PalDescriptorType descriptorType; /**< (eg. `PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE`).*/ +} PalDescriptorSetLayoutBinding; + +/** + * @struct PalDescriptorPoolBindingSize + * @brief Descriptor pool binding size. + * Describes the sizes of each descriptor type in the descriptor pool. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t bindingCount; /**< Number of bindings of `descriptorType`.*/ + PalDescriptorType descriptorType; /**< (eg. `PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE`).*/ +} PalDescriptorPoolBindingSize; + +/** + * @struct PalDescriptorBufferInfo + * @brief Information about a buffer descriptor. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBuffer* buffer; /**< Buffer associated with the descriptor.*/ + uint64_t offset; /**< Offset in bytes. If structured, this will be divided by `stride`.*/ + uint64_t size; /**< Size of the buffer in bytes.*/ + uint64_t stride; /**< For structured buffers. This will be ignored if not supported.*/ +} PalDescriptorBufferInfo; + +/** + * @struct PalDescriptorImageViewInfo + * @brief Information about an image view descriptor. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalImageView* imageView; /**< Image view associated with the descriptor.*/ +} PalDescriptorImageViewInfo; + +/** + * @struct PalDescriptorSamplerInfo + * @brief Information about a sampler descriptor. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalSampler* sampler; /**< Sampler associated with the descriptor.*/ +} PalDescriptorSamplerInfo; + +/** + * @struct PalDescriptorTLASInfo + * @brief Information about a TLAS descriptor. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalAccelerationStructure* tlas; /**< TLAS associated with the descriptor.*/ +} PalDescriptorTLASInfo; + +/** + * @struct PalDescriptorSetWriteInfo + * @brief Write information of a descriptor set. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDescriptorSet* descriptorSet; /**< Descriptor set to write into.*/ + PalDescriptorBufferInfo* bufferInfos; /**< Used with buffer descriptors.*/ + PalDescriptorImageViewInfo* imageViewInfos; /**< Used with image descriptors.*/ + PalDescriptorSamplerInfo* samplerInfos; /**< Used with sampler descriptors.*/ + PalDescriptorTLASInfo* tlasInfos; /**< Used with TLAS descriptors.*/ + PalDescriptorType descriptorType; /**< (eg. `PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE`).*/ + uint32_t layoutBindingIndex; /**< Index into the descriptor set layout bindings.*/ + uint32_t arrayElement; /**< First index within the descriptor set layout bindings.*/ + uint32_t descriptorCount; /**< Number of descriptors to write.*/ +} PalDescriptorSetWriteInfo; + +/** + * @struct PalBarrierInfo + * @brief Information about a barrier. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalUsageState oldState; /**< (eg. `PAL_USAGE_STATE_COLOR_ATTACHMENT`).*/ + PalUsageState newState; /**< (eg. `PAL_USAGE_STATE_PRESENT`).*/ + PalPipelineStages srcStages; /**< (eg. `PAL_PIPELINE_STAGE_COLOR_ATTACHMENT`).*/ + PalPipelineStages dstStages; /**< (eg. `PAL_PIPELINE_STAGE_COLOR_OUTPUT`).*/ +} PalBarrierInfo; + +/** + * @struct PalPushConstantInfo + * @brief Push constant range. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint32_t offset; /**< Offset in bytes.*/ + uint32_t size; /**< Size in bytes.*/ +} PalPushConstantInfo; + +/** + * @struct PalImageSubresourceRange + * @brief Subresource range for images and image views. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalImageAspect aspect; /**< (eg. `PAL_IMAGE_ASPECT_COLOR`).*/ + uint32_t startMipLevel; /**< Start mipmap level. 0 for default.*/ + uint32_t mipLevelCount; /**< Number of mipmap levels.*/ + uint32_t startArrayLayer; /**< Start array layer. 0 for default.*/ + uint32_t layerArrayCount; /**< Number of array layers.*/ +} PalImageSubresourceRange; + +/** + * @struct PalBufferCopyInfo + * @brief Information for buffer to buffer copies. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t size; /**< Size in bytes to copy from source buffer.*/ + uint64_t dstOffset; /**< Offset in bytes in destination buffer.*/ + uint64_t srcOffset; /**< Offset in bytes in source buffer.*/ +} PalBufferCopyInfo; + +/** + * @struct PalBufferImageCopyInfo + * @brief Information for image to buffer and vice versa copies. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t bufferOffset; /**< Offset in bytes into the buffer.*/ + PalImageAspect imageAspect; /**< (eg. `PAL_IMAGE_ASPECT_COLOR`).*/ + uint32_t bufferRowLength; /**< Buffer row length in texels.*/ + uint32_t bufferImageHeight; /**< Buffer image height in texels.*/ + uint32_t ImageMipLevel; /**< Mipmap level of the image.*/ + uint32_t ImageStartArrayLayer; /**< Starting array layer of the image.*/ + uint32_t ImageArrayLayerCount; /**< Number of array layers of the image.*/ + int32_t imageOffsetX; /**< X image offset in bytes.*/ + int32_t imageOffsetY; /**< Y image offset in bytes.*/ + int32_t imageOffsetZ; /**< Z image offset in bytes.*/ + uint32_t imageWidth; /**< Image width in bytes.*/ + uint32_t imageHeight; /**< Image height in bytes.*/ + uint32_t imageDepth; /**< Image depth in bytes.*/ +} PalBufferImageCopyInfo; + +/** + * @struct PalImageCopyInfo + * @brief Information for image to image copies. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalImageAspect aspect; /**< (eg. `PAL_IMAGE_ASPECT_COLOR`).*/ + uint32_t dstMipLevel; /**< Mipmap level of destination image.*/ + uint32_t srcMipLevel; /**< Mipmap level of source image.*/ + uint32_t dstStartArrayLayer; /**< Starting array layer of destination image.*/ + uint32_t srcStartArrayLayer; /**< Starting array layer of source image.*/ + uint32_t arrayLayerCount; /**< Number of array layers of destination and source images.*/ + int32_t dstOffsetX; /**< X destination image offset in bytes.*/ + int32_t srcOffsetX; /**< X source image offset in bytes.*/ + int32_t dstOffsetY; /**< Y destination image offset in bytes.*/ + int32_t srcOffsetY; /**< Y source image offset in bytes.*/ + int32_t dstOffsetZ; /**< Z destination image offset in bytes.*/ + int32_t srcOffsetZ; /**< Z source image offset in bytes.*/ + uint32_t width; /**< Width of the region to copy from source image.*/ + uint32_t height; /**< Height of the region to copy from source image.*/ + uint32_t depth; /**< Depth of the region to copy from source image.*/ +} PalImageCopyInfo; + +/** + * @struct PalShaderBindingTableRecordInfo + * @brief Information for image to image copies. + * + * Uninitialized fields may result in undefined behavior. + * + * The records array must be in this order [raygen][miss][hitgroup][callable]. + * + * @since 2.0 + */ +typedef struct { + void* localData; /**< Must not be `nullptr` if `localDataSize` is not 0.*/ + uint32_t groupIndex; /**< Index into the shader groups used to create the pipeline.*/ + uint32_t localDataSize; /**< Must not be greater than the data size of the group.*/ +} PalShaderBindingTableRecordInfo; + +/** + * @struct PalShaderEntryInfo + * @brief Entry information of a shader. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const char* entryName; /**< Shader stage entry name.*/ + PalShaderStage stage; /**< (eg. `PAL_SHADER_STAGE_VERTEX`).*/ + uint32_t patchControlPoints; /**< For tessellation shaders. Will be ignored by other stages.*/ +} PalShaderEntryInfo; + +/** + * @struct PalImageCreateInfo + * @brief Creation parameters for an image. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalImageUsages usages; /**< (eg. `PAL_IMAGE_USAGE_COLOR` | `PAL_IMAGE_USAGE_TRANSFER_DST`).*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ + uint32_t depth; /**< Depth in pixels.*/ + uint32_t arrayLayerCount; /**< Number of array layers.*/ + uint32_t mipLevelCount; /**< Number of mipmap levels.*/ + PalSampleCount sampleCount; /**< (eg. `PAL_SAMPLE_COUNT_1`).*/ + PalImageType type; /**< (eg. `PAL_IMAGE_TYPE_2D`).*/ + PalFormat format; /**< (eg. `PAL_FORMAT_B8G8R8A8_UNORM`).*/ + PalImageMemoryUsage memoryUsage; /**< See `PalImageMemoryUsage`.*/ +} PalImageCreateInfo; + +/** + * @struct PalImageCreateInfo + * @brief Creation parameters for an image view. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalFormat format; /**< (eg. `PAL_FORMAT_B8G8R8A8_UNORM`).*/ + PalImageViewType type; /**< (eg. `PAL_IMAGE_VIEW_TYPE_2D`).*/ + PalImageSubresourceRange subresourceRange; /**< Range of the image to create the view with.*/ +} PalImageViewCreateInfo; + +/** + * @struct PalSamplerCreateInfo + * @brief Creation parameters for a sampler. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBool enableCompare; /**< `PAL_TRUE` to enable compare operations.*/ + PalBool enableAnisotropy; /**< `PAL_TRUE` to enable texture filtering.*/ + float mipLodBias; /**< Mipmap level bias.*/ + float minLod; /**< Minimum Mipmap level allowed.*/ + float maxLod; /**< Maximum Mipmap level allowed.*/ + float maxAnisotropy; /**< Texture filtering level.*/ + PalFilterMode minFilterMode; /**< (eg. `PAL_FILTER_MODE_LINEAR`).*/ + PalFilterMode magFilterMode; /**< (eg. `PAL_FILTER_MODE_LINEAR`).*/ + PalSamplerMipmapMode mipmapMode; /**< (eg. `PAL_SAMPLER_MIPMAP_MODE_LINEAR`).*/ + PalSamplerAddressMode addressModeU; /**< (eg. `PAL_SAMPLER_ADDRESS_MODE_REPEAT`).*/ + PalSamplerAddressMode addressModeV; /**< (eg. `PAL_SAMPLER_ADDRESS_MODE_REPEAT`).*/ + PalSamplerAddressMode addressModeW; /**< (eg. `PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE`).*/ + PalCompareOp compareOp; /**< (eg. `PAL_COMPARE_OP_GREATER`).*/ + PalBorderColor borderColor; /**< (eg. `PAL_BORDER_COLOR_FLOAT_OPAQUE_BLACK`).*/ +} PalSamplerCreateInfo; + +/** + * @struct PalSwapchainCreateInfo + * @brief Creation parameters for a swapchain. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBool clipped; /**< `PAL_TRUE` to discard pixels that are not visible.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ + uint32_t imageCount; /**< Number of images or back buffers.*/ + uint32_t imageArrayLayerCount; /**< Number of array layers.*/ + PalPresentMode presentMode; /**< (eg. `PAL_PRESENT_MODE_FIFO`).*/ + PalCompositeAplha compositeAlpha; /**< (eg. `PAL_COMPOSITE_ALPHA_OPAQUE`).*/ + PalSurfaceFormat format; /**< (eg. `PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR`).*/ +} PalSwapchainCreateInfo; + +/** + * @struct PalShaderCreateInfo + * @brief Creation parameters for a shader. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + void* code; /**< Pointer to the shader code.*/ + PalShaderEntryInfo* entries; /**< Shader entries.*/ + uint32_t codeSize; /**< Size of `code` in bytes.*/ + uint32_t entryCount; /**< Number of shader entries.*/ +} PalShaderCreateInfo; + +/** + * @struct PalBufferCreateInfo + * @brief Creation parameters for a buffer. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + uint64_t size; /**< Size in bytes.*/ + PalBufferUsages usages; /**< (eg. `PAL_BUFFER_USAGE_VERTEX`).*/ + PalBufferMemoryUsage memoryUsage; /**< See `PalBufferMemoryUsage`.*/ +} PalBufferCreateInfo; + +/** + * @struct PalAccelerationStructureCreateInfo + * @brief Creation parameters for an acceleration structure. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalBuffer* buffer; /**< Acceleration structure buffer.*/ + uint64_t offset; /**< Size in bytes.*/ + uint64_t size; /**< Offset in bytes.*/ + PalAccelerationStructureType type; /**< (eg. `PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL`).*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalAccelerationStructureCreateInfo; + +/** + * @struct PalDescriptorSetLayoutCreateInfo + * @brief Creation parameters for a descriptor set layout. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDescriptorSetLayoutBinding* bindings; /**< Bindings.*/ + PalDescriptorIndexingFlags flags; /**< See `PalDescriptorIndexingFlags`.*/ + uint32_t bindingCount; /**< Number of bindings.*/ +} PalDescriptorSetLayoutCreateInfo; + +/** + * @struct PalDescriptorPoolCreateInfo + * @brief Creation parameters for a descriptor pool. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDescriptorPoolBindingSize* bindingSizes; /**< Binding sizes.*/ + uint32_t bindingSizeCount; /**< Number of bindings sizes.*/ + uint32_t maxDescriptorSets; /**< Maximum number of descriptor sets that can be allocated.*/ + PalDescriptorIndexingFlags flags; /**< See `PalDescriptorIndexingFlags`.*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalDescriptorPoolCreateInfo; + +/** + * @struct PalPipelineLayoutCreateInfo + * @brief Creation parameters for a pipeline layout. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalDescriptorSetLayout** descriptorSetLayouts; /**< Descriptor set layouts.*/ + PalPushConstantInfo pushConstantInfo; /**< Push constant info.*/ + uint32_t descriptorSetLayoutCount; /**< Number of descriptor set layouts.*/ + PalBool usePushConstant; /**< `PAL_TRUE` to use push constant.*/ +} PalPipelineLayoutCreateInfo; + +/** + * @struct PalGraphicsPipelineCreateInfo + * @brief Creation parameters for a graphics pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalPipelineLayout* pipelineLayout; /**< Pipeline layout.*/ + PalShader** shaders; /**< Shaders.*/ + PalVertexLayout* vertexLayouts; /**< Vertex layouts.*/ + PalColorBlendAttachment* colorBlendAttachments; /**< Color blend attachments.*/ + PalRasterizerState* rasterizerState; /**< Rasterizer state.*/ + PalMultisampleState* multisampleState; /**< Multisample state.*/ + PalDepthStencilState* depthStencilState; /**< Depth stencil state.*/ + PalFragmentShadingRateState* fragmentShadingRateState; /**< Fragment shading rate state.*/ + PalRenderingLayoutInfo* renderingLayout; /**< Rendering layout.*/ + PalBool primitiveRestartEnable; /**< `PAL_TRUE` to enable primitive restart for indexed draw.*/ + uint32_t vertexLayoutCount; /**< Number of vertex layouts.*/ + uint32_t colorBlendAttachmentCount; /**< Number of color attachments.*/ + uint32_t shaderCount; /**< Number of shaders.*/ + PalIndexType indexType; /**< Will be used if `primitiveRestartEnable` is `PAL_TRUE`.*/ + PalPrimitiveTopology topology; /**< (eg. `PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST`).*/ +} PalGraphicsPipelineCreateInfo; + +/** + * @struct PalComputePipelineCreateInfo + * @brief Creation parameters for a compute pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalPipelineLayout* pipelineLayout; /**< Pipeline layout.*/ + PalShader* computeShader; /**< Compute shader.*/ +} PalComputePipelineCreateInfo; + +/** + * @struct PalRayTracingShaderGroupCreateInfo + * @brief Creation parameters for a ray tracing pipeline shader group. + * + * Uninitialized fields may result in undefined behavior. + * + * The shader group array must be in this order [raygen][miss][hitgroup][callable]. + * + * @since 2.0 + */ +typedef struct { + PalRayTracingShaderGroupType type; /**< (eg. `PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL`).*/ + uint32_t anyHitShaderIndex; /**< Index of the anyhit shader.*/ + uint32_t anyHitShaderEntryIndex; /**< Index of the anyhit shader entry.*/ + uint32_t closestHitShaderIndex; /**< Index of the closest hit shader.*/ + uint32_t closestHitShaderEntryIndex; /**< Index of the closest hit shader entry.*/ + uint32_t generalShaderIndex; /**< Index of the general shader.*/ + uint32_t generalShaderEntryIndex; /**< Index of the general shader entry.*/ + uint32_t intersectionShaderIndex; /**< Index of the intersection shader.*/ + uint32_t intersectionShaderEntryIndex; /**< Index of the intersection shader entry.*/ + uint32_t maxDataSize; /**< Size of extra data associated with the shader group.*/ +} PalRayTracingShaderGroupCreateInfo; + +/** + * @struct PalRayTracingPipelineCreateInfo + * @brief Creation parameters for a ray tracing pipeline. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalPipelineLayout* pipelineLayout; /**< Pipeline layout.*/ + PalRayTracingShaderGroupCreateInfo* shaderGroups; /**< Shader groups.*/ + PalShader** shaders; /**< Shaders.*/ + uint32_t shaderGroupCount; /**< Number of shader groups.*/ + uint32_t shaderCount; /**< Number of shaders.*/ + uint32_t maxRecursionDepth; /**< Max number of ray recursion.*/ + uint32_t maxAttributeSize; /**< Max attributes size in bytes.*/ + uint32_t maxPayloadSize; /**< Max payload size in bytes.*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalRayTracingPipelineCreateInfo; + +/** + * @struct PalShaderBindingTableCreateInfo + * @brief Creation parameters for a shader binding table. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalShaderBindingTableRecordInfo* records; /**< Shader binding table records.*/ + PalPipeline* rayTracingPipeline; /**< Ray tracing pipeline.*/ + uint32_t recordCount; /**< Number of shader binding table records.*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalShaderBindingTableCreateInfo; + +/** + * @struct PalGraphicsBackendRegistrationInfo + * @brief Custom graphics backend information. + * + * Uninitialized fields may result in undefined behavior. + * + * All backend handle implementation (eg. struct CustomBuffer) must reserve its first field as + * a `void*`. This will be used by the graphics layer. + * + * Each backend Vtable version (eg. `PAL_GRAPHICS_BACKEND_VTABLE_VERSION_1`) has required functions + * that must be present implemented. This will be validated at initialization. See version constant + * for the required functions. Optional functions have their own requirements. + * + * @since 2.0 + */ +typedef struct { + const void* vtable; /**< Pointer to the backend vtable.*/ + PalGraphicsBackendVtableVersion version; /**< (eg. `PAL_GRAPHICS_BACKEND_VTABLE_VERSION_1`).*/ + uint32_t reserved; /**< Must be set to 0.*/ +} PalGraphicsBackendInfo; + +/** + * @struct PalGraphicsBackendVtable1 + * @brief Version 1 dispatch table for PAL graphics system backends. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + /** + * Backend implementation of ::palEnumerateAdapters. + * + * Must obey the rules and semantics documented in palEnumerateAdapters(). + */ + PalResult(PAL_CALL* enumerateAdapters)( + uint32_t* count, + PalAdapter** outAdapters); + + /** + * Backend implementation of ::palGetAdapterInfo. + * + * Must obey the rules and semantics documented in palGetAdapterInfo(). + */ + void(PAL_CALL* getAdapterInfo)( + PalAdapter* adapter, + PalAdapterInfo* info); + + /** + * Backend implementation of ::palGetAdapterCapabilities. + * + * Must obey the rules and semantics documented in palGetAdapterCapabilities(). + */ + void(PAL_CALL* getAdapterCapabilities)( + PalAdapter* adapter, + PalAdapterCapabilities* caps); + + /** + * Backend implementation of ::palGetAdapterFeatures. + * + * Must obey the rules and semantics documented in palGetAdapterFeatures(). + */ + PalAdapterFeatures(PAL_CALL* getAdapterFeatures)(PalAdapter* adapter); + + /** + * Backend implementation of ::palGetHighestSupportedShaderTarget. + * + * Must obey the rules and semantics documented in palGetHighestSupportedShaderTarget(). + */ + uint32_t(PAL_CALL* getHighestSupportedShaderTarget)( + PalAdapter* adapter, + PalShaderFormats shaderFormat); + + /** + * Backend implementation of ::palCreateDevice. + * + * Must obey the rules and semantics documented in palCreateDevice(). + */ + PalResult(PAL_CALL* createDevice)( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice); + + /** + * Backend implementation of ::palDestroyDevice. + * + * Must obey the rules and semantics documented in palDestroyDevice(). + */ + void(PAL_CALL* destroyDevice)(PalDevice* device); + + /** + * Backend implementation of ::palGetDeviceLostReason. + * + * Must obey the rules and semantics documented in palGetDeviceLostReason(). + */ + uint32_t(PAL_CALL* getDeviceLostReason)(PalDevice* device); + + /** + * Backend implementation of ::palAllocateMemory. + * + * Must obey the rules and semantics documented in palAllocateMemory(). + */ + PalResult(PAL_CALL* allocateMemory)( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory); + + /** + * Backend implementation of ::palFreeMemory. + * + * Must obey the rules and semantics documented in palFreeMemory(). + */ + void(PAL_CALL* freeMemory)(PalMemory* memory); + + /** + * Backend implementation of ::palQuerySamplerAnisotropyCapabilities. + * + * Must obey the rules and semantics documented in + * palQuerySamplerAnisotropyCapabilities(). + */ + void(PAL_CALL* querySamplerAnisotropyCapabilities)( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps); + + /** + * Backend implementation of ::palQueryMultiViewCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryMultiViewCapabilities(). + */ + void(PAL_CALL* queryMultiViewCapabilities)( + PalDevice* device, + PalMultiViewCapabilities* caps); + + /** + * Backend implementation of ::palQueryMultiViewportCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryMultiViewportCapabilities(). + */ + void(PAL_CALL* queryMultiViewportCapabilities)( + PalDevice* device, + PalMultiViewportCapabilities* caps); + + /** + * Backend implementation of ::palQueryDepthStencilCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryDepthStencilCapabilities(). + */ + void(PAL_CALL* queryDepthStencilCapabilities)( + PalDevice* device, + PalDepthStencilCapabilities* caps); + + /** + * Backend implementation of ::palQueryFragmentShadingRateCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryFragmentShadingRateCapabilities(). + */ + void(PAL_CALL* queryFragmentShadingRateCapabilities)( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps); + + /** + * Backend implementation of ::palQueryMeshShaderCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryMeshShaderCapabilities(). + */ + void(PAL_CALL* queryMeshShaderCapabilities)( + PalDevice* device, + PalMeshShaderCapabilities* caps); + + /** + * Backend implementation of ::palQueryRayTracingCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryRayTracingCapabilities(). + */ + void(PAL_CALL* queryRayTracingCapabilities)( + PalDevice* device, + PalRayTracingCapabilities* caps); + + /** + * Backend implementation of ::palQueryDescriptorIndexingCapabilities. + * + * Must obey the rules and semantics documented in + * palQueryDescriptorIndexingCapabilities(). + */ + void(PAL_CALL* queryDescriptorIndexingCapabilities)( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps); + + /** + * Backend implementation of ::palCreateQueue. + * + * Must obey the rules and semantics documented in palCreateQueue(). + */ + PalResult(PAL_CALL* createQueue)( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue); + + /** + * Backend implementation of ::palDestroyQueue. + * + * Must obey the rules and semantics documented in palDestroyQueue(). + */ + void(PAL_CALL* destroyQueue)(PalQueue* queue); + + /** + * Backend implementation of ::palCanQueuePresent. + * + * Must obey the rules and semantics documented in palCanQueuePresent(). + */ + PalBool(PAL_CALL* canQueuePresent)( + PalQueue* queue, + PalSurface* surface); + + /** + * Backend implementation of ::palWaitQueue. + * + * Must obey the rules and semantics documented in palWaitQueue(). + */ + PalResult(PAL_CALL* waitQueue)(PalQueue* queue); + + /** + * Backend implementation of ::palEnumerateFormats. + * + * Must obey the rules and semantics documented in palEnumerateFormats(). + */ + void(PAL_CALL* enumerateFormats)( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats); + + /** + * Backend implementation of ::palIsFormatSupported. + * + * Must obey the rules and semantics documented in palIsFormatSupported(). + */ + PalBool(PAL_CALL* isFormatSupported)( + PalAdapter* adapter, + PalFormat format); + + /** + * Backend implementation of ::palQueryFormatImageUsages. + * + * Must obey the rules and semantics documented in palQueryFormatImageUsages(). + */ + PalImageUsages(PAL_CALL* queryFormatImageUsages)( + PalAdapter* adapter, + PalFormat format); + + /** + * Backend implementation of ::palQueryFormatSampleCount. + * + * Must obey the rules and semantics documented in palQueryFormatSampleCount(). + */ + PalSampleCount(PAL_CALL* queryFormatSampleCount)( + PalAdapter* adapter, + PalFormat format); + + /** + * Backend implementation of ::palCreateImage. + * + * Must obey the rules and semantics documented in palCreateImage(). + */ + PalResult(PAL_CALL* createImage)( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage); + + /** + * Backend implementation of ::palDestroyImage. + * + * Must obey the rules and semantics documented in palDestroyImage(). + */ + void(PAL_CALL* destroyImage)(PalImage* image); + + /** + * Backend implementation of ::palGetImageInfo. + * + * Must obey the rules and semantics documented in palGetImageInfo(). + */ + void(PAL_CALL* getImageInfo)( + PalImage* image, + PalImageInfo* info); + + /** + * Backend implementation of ::palGetImageMemoryRequirements. + * + * Must obey the rules and semantics documented in palGetImageMemoryRequirements(). + */ + void(PAL_CALL* getImageMemoryRequirements)( + PalImage* image, + PalMemoryRequirements* requirements); + + /** + * Backend implementation of ::palBindImageMemory. + * + * Must obey the rules and semantics documented in palBindImageMemory(). + */ + PalResult(PAL_CALL* bindImageMemory)( + PalImage* image, + PalMemory* memory, + uint64_t offset); + + /** + * Backend implementation of ::palCreateImageView. + * + * Must obey the rules and semantics documented in palCreateImageView(). + */ + PalResult(PAL_CALL* createImageView)( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView); + + /** + * Backend implementation of ::palDestroyImageView. + * + * Must obey the rules and semantics documented in palDestroyImageView(). + */ + void(PAL_CALL* destroyImageView)(PalImageView* imageView); + + /** + * Backend implementation of ::palCreateSampler. + * + * Must obey the rules and semantics documented in palCreateSampler(). + */ + PalResult(PAL_CALL* createSampler)( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler); + + /** + * Backend implementation of ::palDestroySampler. + * + * Must obey the rules and semantics documented in palDestroySampler(). + */ + void(PAL_CALL* destroySampler)(PalSampler* sampler); + + /** + * Backend implementation of ::palCreateSurface. + * + * Must obey the rules and semantics documented in palCreateSurface(). + */ + PalResult(PAL_CALL* createSurface)( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface); + + /** + * Backend implementation of ::palDestroySurface. + * + * Must obey the rules and semantics documented in palDestroySurface(). + */ + void(PAL_CALL* destroySurface)(PalSurface* surface); + + /** + * Backend implementation of ::palGetSurfaceCapabilities. + * + * Must obey the rules and semantics documented in palGetSurfaceCapabilities(). + */ + void(PAL_CALL* getSurfaceCapabilities)( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps); + + /** + * Backend implementation of ::palCreateSwapchain. + * + * Must obey the rules and semantics documented in palCreateSwapchain(). + */ + PalResult(PAL_CALL* createSwapchain)( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain); + + /** + * Backend implementation of ::palDestroySwapchain. + * + * Must obey the rules and semantics documented in palDestroySwapchain(). + */ + void(PAL_CALL* destroySwapchain)(PalSwapchain* swapchain); + + /** + * Backend implementation of ::palGetSwapchainImage. + * + * Must obey the rules and semantics documented in palGetSwapchainImage(). + */ + PalImage*(PAL_CALL* getSwapchainImage)( + PalSwapchain* swapchain, + uint32_t index); + + /** + * Backend implementation of ::palGetNextSwapchainImage. + * + * Must obey the rules and semantics documented in palGetNextSwapchainImage(). + */ + PalResult(PAL_CALL* getNextSwapchainImage)( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex); + + /** + * Backend implementation of ::palPresentSwapchain. + * + * Must obey the rules and semantics documented in palPresentSwapchain(). + */ + PalResult(PAL_CALL* presentSwapchain)( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore); + + /** + * Backend implementation of ::palResizeSwapchain. + * + * Must obey the rules and semantics documented in palResizeSwapchain(). + */ + PalResult(PAL_CALL* resizeSwapchain)( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight); + + /** + * Backend implementation of ::palCreateShader. + * + * Must obey the rules and semantics documented in palCreateShader(). + */ + PalResult(PAL_CALL* createShader)( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader); + + /** + * Backend implementation of ::palDestroyShader. + * + * Must obey the rules and semantics documented in palDestroyShader(). + */ + void(PAL_CALL* destroyShader)(PalShader* shader); + + /** + * Backend implementation of ::palCreateFence. + * + * Must obey the rules and semantics documented in palCreateFence(). + */ + PalResult(PAL_CALL* createFence)( + PalDevice* device, + PalBool signaled, + PalFence** outFence); + + /** + * Backend implementation of ::palDestroyFence. + * + * Must obey the rules and semantics documented in palDestroyFence(). + */ + void(PAL_CALL* destroyFence)(PalFence* fence); + + /** + * Backend implementation of ::palWaitFence. + * + * Must obey the rules and semantics documented in palWaitFence(). + */ + PalResult(PAL_CALL* waitFence)( + PalFence* fence, + uint64_t timeout); + + /** + * Backend implementation of ::palResetFence. + * + * Must obey the rules and semantics documented in palResetFence(). + */ + PalResult(PAL_CALL* resetFence)(PalFence* fence); + + /** + * Backend implementation of ::palIsFenceSignaled. + * + * Must obey the rules and semantics documented in palIsFenceSignaled(). + */ + PalBool(PAL_CALL* isFenceSignaled)(PalFence* fence); + + /** + * Backend implementation of ::palCreateSemaphore. + * + * Must obey the rules and semantics documented in palCreateSemaphore(). + */ + PalResult(PAL_CALL* createSemaphore)( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore); + + /** + * Backend implementation of ::palDestroySemaphore. + * + * Must obey the rules and semantics documented in palDestroySemaphore(). + */ + void(PAL_CALL* destroySemaphore)(PalSemaphore* semaphore); + + /** + * Backend implementation of ::palWaitSemaphore. + * + * Must obey the rules and semantics documented in palWaitSemaphore(). + */ + PalResult(PAL_CALL* waitSemaphore)( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout); + + /** + * Backend implementation of ::palSignalSemaphore. + * + * Must obey the rules and semantics documented in palSignalSemaphore(). + */ + PalResult(PAL_CALL* signalSemaphore)( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value); + + /** + * Backend implementation of ::palGetSemaphoreValue. + * + * Must obey the rules and semantics documented in palGetSemaphoreValue(). + */ + PalResult(PAL_CALL* getSemaphoreValue)( + PalSemaphore* semaphore, + uint64_t* value); + + /** + * Backend implementation of ::palCreateCommandPool. + * + * Must obey the rules and semantics documented in palCreateCommandPool(). + */ + PalResult(PAL_CALL* createCommandPool)( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool); + + /** + * Backend implementation of ::palDestroyCommandPool. + * + * Must obey the rules and semantics documented in palDestroyCommandPool(). + */ + void(PAL_CALL* destroyCommandPool)(PalCommandPool* pool); + + /** + * Backend implementation of ::palAllocateCommandBuffer. + * + * Must obey the rules and semantics documented in palAllocateCommandBuffer(). + */ + PalResult(PAL_CALL* allocateCommandBuffer)( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer); + + /** + * Backend implementation of ::palFreeCommandBuffer. + * + * Must obey the rules and semantics documented in palFreeCommandBuffer(). + */ + void(PAL_CALL* freeCommandBuffer)(PalCommandBuffer* cmdBuffer); + + /** + * Backend implementation of ::palResetCommandBuffer. + * + * Must obey the rules and semantics documented in palResetCommandBuffer(). + */ + PalResult(PAL_CALL* resetCommandBuffer)(PalCommandBuffer* cmdBuffer); + + /** + * Backend implementation of ::palSubmitCommandBuffer. + * + * Must obey the rules and semantics documented in palSubmitCommandBuffer(). + */ + PalResult(PAL_CALL* submitCommandBuffer)( + PalQueue* queue, + PalCommandBufferSubmitInfo* info); + + /** + * Backend implementation of ::palCmdBegin. + * + * Must obey the rules and semantics documented in palCmdBegin(). + */ + PalResult(PAL_CALL* cmdBegin)( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info); + + /** + * Backend implementation of ::palCmdEnd. + * + * Must obey the rules and semantics documented in palCmdEnd(). + */ + PalResult(PAL_CALL* cmdEnd)(PalCommandBuffer* cmdBuffer); + + /** + * Backend implementation of ::palCmdExecuteCommandBuffer. + * + * Must obey the rules and semantics documented in palCmdExecuteCommandBuffer(). + */ + void(PAL_CALL* cmdExecuteCommandBuffer)( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer); + + /** + * Backend implementation of ::palCmdSetFragmentShadingRate. + * + * Must obey the rules and semantics documented in palCmdSetFragmentShadingRate(). + */ + void(PAL_CALL* cmdSetFragmentShadingRate)( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state); + + /** + * Backend implementation of ::palCmdDrawMeshTasks. + * + * Must obey the rules and semantics documented in palCmdDrawMeshTasks(). + */ + void(PAL_CALL* cmdDrawMeshTasks)( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + + /** + * Backend implementation of ::palCmdDrawMeshTasksIndirect. + * + * Must obey the rules and semantics documented in palCmdDrawMeshTasksIndirect(). + */ + void(PAL_CALL* cmdDrawMeshTasksIndirect)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount); + + /** + * Backend implementation of ::palCmdDrawMeshTasksIndirectCount. + * + * Must obey the rules and semantics documented in palCmdDrawMeshTasksIndirectCount(). + */ + void(PAL_CALL* cmdDrawMeshTasksIndirectCount)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + + /** + * Backend implementation of ::palCmdBuildAccelerationStructure. + * + * Must obey the rules and semantics documented in palCmdBuildAccelerationStructure(). + */ + void(PAL_CALL* cmdBuildAccelerationStructure)( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info); + + /** + * Backend implementation of ::palCmdBeginRendering. + * + * Must obey the rules and semantics documented in palCmdBeginRendering(). + */ + void(PAL_CALL* cmdBeginRendering)( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info); + + /** + * Backend implementation of ::palCmdEndRendering. + * + * Must obey the rules and semantics documented in palCmdEndRendering(). + */ + void(PAL_CALL* cmdEndRendering)(PalCommandBuffer* cmdBuffer); + + /** + * Backend implementation of ::palCmdCopyBuffer. + * + * Must obey the rules and semantics documented in palCmdCopyBuffer(). + */ + void(PAL_CALL* cmdCopyBuffer)( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo); + + /** + * Backend implementation of ::palCmdCopyBufferToImage. + * + * Must obey the rules and semantics documented in palCmdCopyBufferToImage(). + */ + void(PAL_CALL* cmdCopyBufferToImage)( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo); + + /** + * Backend implementation of ::cmdCopyImage. + * + * Must obey the rules and semantics documented in cmdCopyImage(). + */ + void(PAL_CALL* cmdCopyImage)( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo); + + /** + * Backend implementation of ::palCmdCopyImageToBuffer. + * + * Must obey the rules and semantics documented in palCmdCopyImageToBuffer(). + */ + void(PAL_CALL* cmdCopyImageToBuffer)( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo); + + /** + * Backend implementation of ::palCmdBindPipeline. + * + * Must obey the rules and semantics documented in palCmdBindPipeline(). + */ + void(PAL_CALL* cmdBindPipeline)( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline); + + /** + * Backend implementation of ::palCmdSetViewport. + * + * Must obey the rules and semantics documented in palCmdSetViewport(). + */ + void(PAL_CALL* cmdSetViewport)( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports); + + /** + * Backend implementation of ::palCmdSetScissors. + * + * Must obey the rules and semantics documented in palCmdSetScissors(). + */ + void(PAL_CALL* cmdSetScissors)( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors); + + /** + * Backend implementation of ::palCmdBindVertexBuffers. + * + * Must obey the rules and semantics documented in palCmdBindVertexBuffers(). + */ + void(PAL_CALL* cmdBindVertexBuffers)( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets); + + /** + * Backend implementation of ::palCmdBindIndexBuffer. + * + * Must obey the rules and semantics documented in palCmdBindIndexBuffer(). + */ + void(PAL_CALL* cmdBindIndexBuffer)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type); + + /** + * Backend implementation of ::palCmdDraw. + * + * Must obey the rules and semantics documented in palCmdDraw(). + */ + void(PAL_CALL* cmdDraw)( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + + /** + * Backend implementation of ::palCmdDrawIndirect. + * + * Must obey the rules and semantics documented in palCmdDrawIndirect(). + */ + void(PAL_CALL* cmdDrawIndirect)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + + /** + * Backend implementation of ::palCmdDrawIndirectCount. + * + * Must obey the rules and semantics documented in palCmdDrawIndirectCount(). + */ + void(PAL_CALL* cmdDrawIndirectCount)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + + /** + * Backend implementation of ::palCmdDrawIndexed. + * + * Must obey the rules and semantics documented in palCmdDrawIndexed(). + */ + void(PAL_CALL* cmdDrawIndexed)( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + + /** + * Backend implementation of ::palCmdDrawIndexedIndirect. + * + * Must obey the rules and semantics documented in palCmdDrawIndexedIndirect(). + */ + void(PAL_CALL* cmdDrawIndexedIndirect)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + + /** + * Backend implementation of ::palCmdDrawIndexedIndirectCount. + * + * Must obey the rules and semantics documented in palCmdDrawIndexedIndirectCount(). + */ + void(PAL_CALL* cmdDrawIndexedIndirectCount)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + + /** + * Backend implementation of ::palCmdAccelerationStructureBarrier. + * + * Must obey the rules and semantics documented in palCmdAccelerationStructureBarrier(). + */ + void(PAL_CALL* cmdAccelerationStructureBarrier)( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info); + + /** + * Backend implementation of ::palCmdImageBarrier. + * + * Must obey the rules and semantics documented in palCmdImageBarrier(). + */ + void(PAL_CALL* cmdImageBarrier)( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info); + + /** + * Backend implementation of ::palCmdBufferBarrier. + * + * Must obey the rules and semantics documented in palCmdBufferBarrier(). + */ + void(PAL_CALL* cmdBufferBarrier)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info); + + /** + * Backend implementation of ::palCmdDispatch. + * + * Must obey the rules and semantics documented in palCmdDispatch(). + */ + void(PAL_CALL* cmdDispatch)( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + + /** + * Backend implementation of ::palCmdDispatchBase. + * + * Must obey the rules and semantics documented in palCmdDispatchBase(). + */ + void(PAL_CALL* cmdDispatchBase)( + PalCommandBuffer* cmdBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + + /** + * Backend implementation of ::palCmdDispatchIndirect. + * + * Must obey the rules and semantics documented in palCmdDispatchIndirect(). + */ + void(PAL_CALL* cmdDispatchIndirect)( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer); + + /** + * Backend implementation of ::palCmdTraceRays. + * + * Must obey the rules and semantics documented in palCmdTraceRays(). + */ + void(PAL_CALL* cmdTraceRays)( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth); + + /** + * Backend implementation of ::palCmdTraceRaysIndirect. + * + * Must obey the rules and semantics documented in palCmdTraceRaysIndirect(). + */ + void(PAL_CALL* cmdTraceRaysIndirect)( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer); + + /** + * Backend implementation of ::palCmdBindDescriptorSet. + * + * Must obey the rules and semantics documented in palCmdBindDescriptorSet(). + */ + void(PAL_CALL* cmdBindDescriptorSet)( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set); + + /** + * Backend implementation of ::palCmdPushConstants. + * + * Must obey the rules and semantics documented in palCmdPushConstants(). + */ + void(PAL_CALL* cmdPushConstants)( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value); + + /** + * Backend implementation of ::palCmdSetCullMode. + * + * Must obey the rules and semantics documented in palCmdSetCullMode(). + */ + void(PAL_CALL* cmdSetCullMode)( + PalCommandBuffer* cmdBuffer, + PalCullMode cullMode); + + /** + * Backend implementation of ::palCmdSetFrontFace. + * + * Must obey the rules and semantics documented in palCmdSetFrontFace(). + */ + void(PAL_CALL* cmdSetFrontFace)( + PalCommandBuffer* cmdBuffer, + PalFrontFace frontFace); + + /** + * Backend implementation of ::palCmdSetPrimitiveTopology. + * + * Must obey the rules and semantics documented in palCmdSetPrimitiveTopology(). + */ + void(PAL_CALL* cmdSetPrimitiveTopology)( + PalCommandBuffer* cmdBuffer, + PalPrimitiveTopology topology); + + /** + * Backend implementation of ::palCmdSetDepthTestEnable. + * + * Must obey the rules and semantics documented in palCmdSetDepthTestEnable(). + */ + void(PAL_CALL* cmdSetDepthTestEnable)( + PalCommandBuffer* cmdBuffer, + PalBool enable); + + /** + * Backend implementation of ::palCmdSetDepthWriteEnable. + * + * Must obey the rules and semantics documented in palCmdSetDepthWriteEnable(). + */ + void(PAL_CALL* cmdSetDepthWriteEnable)( + PalCommandBuffer* cmdBuffer, + PalBool enable); + + /** + * Backend implementation of ::palCmdSetStencilOp. + * + * Must obey the rules and semantics documented in palCmdSetStencilOp(). + */ + void(PAL_CALL* cmdSetStencilOp)( + PalCommandBuffer* cmdBuffer, + PalStencilFaceFlags faceMask, + PalStencilOp failOp, + PalStencilOp passOp, + PalStencilOp depthFailOp, + PalCompareOp compareOp); + + /** + * Backend implementation of ::palCreateAccelerationstructure. + * + * Must obey the rules and semantics documented in palCreateAccelerationstructure(). + */ + PalResult(PAL_CALL* createAccelerationstructure)( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs); + + /** + * Backend implementation of ::palDestroyAccelerationStructure. + * + * Must obey the rules and semantics documented in palDestroyAccelerationStructure(). + */ + void(PAL_CALL* destroyAccelerationstructure)(PalAccelerationStructure* as); + + /** + * Backend implementation of ::palGetAccelerationStructureBuildSize. + * + * Must obey the rules and semantics documented in palGetAccelerationStructureBuildSize(). + */ + void(PAL_CALL* getAccelerationStructureBuildSize)( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size); + + /** + * Backend implementation of ::palCreateBuffer. + * + * Must obey the rules and semantics documented in palCreateBuffer(). + */ + PalResult(PAL_CALL* createBuffer)( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer); + + /** + * Backend implementation of ::palDestroyBuffer. + * + * Must obey the rules and semantics documented in palDestroyBuffer(). + */ + void(PAL_CALL* destroyBuffer)(PalBuffer* buffer); + + /** + * Backend implementation of ::palGetBufferMemoryRequirements. + * + * Must obey the rules and semantics documented in palGetBufferMemoryRequirements(). + */ + void(PAL_CALL* getBufferMemoryRequirements)( + PalBuffer* buffer, + PalMemoryRequirements* requirements); + + /** + * Backend implementation of ::palComputeInstanceStagingSize. + * + * Must obey the rules and semantics documented in palComputeInstanceStagingSize(). + */ + void(PAL_CALL* computeInstanceStagingSize)( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize); + + /** + * Backend implementation of ::palComputeImageStagingRequirements. + * + * Must obey the rules and semantics documented in palComputeImageStagingRequirements(). + */ + void(PAL_CALL* computeImageStagingRequirements)( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements); + + /** + * Backend implementation of ::palWriteInstanceStaging. + * + * Must obey the rules and semantics documented in palWriteInstanceStaging(). + */ + void(PAL_CALL* writeInstanceStaging)( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr); + + /** + * Backend implementation of ::palWriteImageStaging. + * + * Must obey the rules and semantics documented in palWriteImageStaging(). + */ + void(PAL_CALL* writeImageStaging)( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr); + + /** + * Backend implementation of ::palBindBufferMemory. + * + * Must obey the rules and semantics documented in palBindBufferMemory(). + */ + PalResult(PAL_CALL* bindBufferMemory)( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset); + + /** + * Backend implementation of ::palMapBuffer. + * + * Must obey the rules and semantics documented in palMapBuffer(). + */ + PalResult(PAL_CALL* mapBuffer)( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr); + + /** + * Backend implementation of ::palUnmapBuffer. + * + * Must obey the rules and semantics documented in palUnmapBuffer(). + */ + void(PAL_CALL* unmapBuffer)(PalBuffer* buffer); + + /** + * Backend implementation of ::palGetBufferDeviceAddress. + * + * Must obey the rules and semantics documented in palGetBufferDeviceAddress(). + */ + PalDeviceAddress(PAL_CALL* getBufferDeviceAddress)(PalBuffer* buffer); + + /** + * Backend implementation of ::palCreateDescriptorSetLayout. + * + * Must obey the rules and semantics documented in palCreateDescriptorSetLayout(). + */ + PalResult(PAL_CALL* createDescriptorSetLayout)( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout); + + /** + * Backend implementation of ::palDestroyDescriptorSetLayout. + * + * Must obey the rules and semantics documented in palDestroyDescriptorSetLayout(). + */ + void(PAL_CALL* destroyDescriptorSetLayout)(PalDescriptorSetLayout* layout); + + /** + * Backend implementation of ::palCreateDescriptorPool. + * + * Must obey the rules and semantics documented in palCreateDescriptorPool(). + */ + PalResult(PAL_CALL* createDescriptorPool)( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool); + + /** + * Backend implementation of ::palDestroyDescriptorPool. + * + * Must obey the rules and semantics documented in palDestroyDescriptorPool(). + */ + void(PAL_CALL* destroyDescriptorPool)(PalDescriptorPool* pool); + + /** + * Backend implementation of ::palResetDescriptorPool. + * + * Must obey the rules and semantics documented in palResetDescriptorPool(). + */ + PalResult(PAL_CALL* resetDescriptorPool)(PalDescriptorPool* pool); + + /** + * Backend implementation of ::palAllocateDescriptorSet. + * + * Must obey the rules and semantics documented in palAllocateDescriptorSet(). + */ + PalResult(PAL_CALL* allocateDescriptorSet)( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet); + + /** + * Backend implementation of ::palUpdateDescriptorSet. + * + * Must obey the rules and semantics documented in palUpdateDescriptorSet(). + */ + PalResult(PAL_CALL* updateDescriptorSet)( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos); + + /** + * Backend implementation of ::palCreatePipelineLayout. + * + * Must obey the rules and semantics documented in palCreatePipelineLayout(). + */ + PalResult(PAL_CALL* createPipelineLayout)( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout); + + /** + * Backend implementation of ::palDestroyPipelineLayout. + * + * Must obey the rules and semantics documented in palDestroyPipelineLayout(). + */ + void(PAL_CALL* destroyPipelineLayout)(PalPipelineLayout* layout); + + /** + * Backend implementation of ::palCreateGraphicsPipeline. + * + * Must obey the rules and semantics documented in palCreateGraphicsPipeline(). + */ + PalResult(PAL_CALL* createGraphicsPipeline)( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline); + + /** + * Backend implementation of ::palCreateComputePipeline. + * + * Must obey the rules and semantics documented in palCreateComputePipeline(). + */ + PalResult(PAL_CALL* createComputePipeline)( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline); + + /** + * Backend implementation of ::palCreateRayTracingPipeline. + * + * Must obey the rules and semantics documented in palCreateRayTracingPipeline(). + */ + PalResult(PAL_CALL* createRayTracingPipeline)( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline); + + /** + * Backend implementation of ::palDestroyPipeline. + * + * Must obey the rules and semantics documented in palDestroyPipeline(). + */ + void(PAL_CALL* destroyPipeline)(PalPipeline* pipeline); + + /** + * Backend implementation of ::palCreateShaderBindingTable. + * + * Must obey the rules and semantics documented in palCreateShaderBindingTable(). + */ + PalResult(PAL_CALL* createShaderBindingTable)( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt); + + /** + * Backend implementation of ::palDestroyShaderBindingTable. + * + * Must obey the rules and semantics documented in palDestroyShaderBindingTable(). + */ + void(PAL_CALL* destroyShaderBindingTable)(PalShaderBindingTable* sbt); + + /** + * Backend implementation of ::palUpdateShaderBindingTable. + * + * Must obey the rules and semantics documented in palUpdateShaderBindingTable(). + */ + void(PAL_CALL* updateShaderBindingTable)( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos); +} PalGraphicsBackendVtable1; + +/** + * @brief Initialize the graphics system. + * + * The debugger, allocator and custom backends will not not copied, therefore the pointers must + * remain valid until the graphics system is shutdown. Set the debugger to `nullptr` to disable + * debugging and validation layers. + * + * If `debugger` is not `nullptr` and there is no debug layers, this function will not fail but + * debugging will be disabled. + * + * All backends must have their vtable functions fully set according to the version requirements. + * All required pointers must be set. If an optional feature is not supported, `nullptr` must be + * set and its appropriate feature bit (eg. `PAL_ADAPTER_FEATURE_RAY_TRACING`) must not be set. + * + * @param[in] debugger Optional debugger. Set to `nullptr` to disable debugging and validation + * layers. + * @param[in] allocator Optional user-provided allocator. Set to `nullptr` to use default. + * @param[in] customBackendCount The number of custom backends in `customBackends`. + * @param[in] customBackends Pointer to an array of custom backends. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palShutdownGraphics + */ +PAL_API PalResult PAL_CALL palInitGraphics( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator, + uint32_t customBackendCount, + const PalGraphicsBackendInfo* customBackends); + +/** + * @brief Shutdown the graphics system. + * + * If the graphics system has not been initialized, the function returns silently. + * All created devices, queues, images, swapchains etc must be destroyed before this call. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palInitGraphics + */ +PAL_API void PAL_CALL palShutdownGraphics(); + +/** + * @brief Returns a list of all adapters (GPU) from custom and internal backends. + * + * The graphics system must be initialized before this call. + * + * If a custom backend which implements an adapter with its API type of Vulkan, and there is an + * adapter from the internal backends with the same specifications, this function will return both + * of them in the list. Use PalAdapterInfo::backendName to differentiate between custom and + * internal backend. the backend name for internal backend is `PAL`. + * + * Call this function first with PalAdapter array set to `nullptr` to get the number of adapters. + * Allocate memory for the PalAdapter array and passed in the count and the allocated array. If + * the count of the array is less than the number of adapters, PAL will write upto that limit. + * + * The adapter handles must not be freed by the user, they are managed by the + * graphics system. Users are required to cache this, and call this function again + * if adapters are added or removed which is rare except for virtual ones. + * + * @param[in, out] count Capacity of the PalAdapter array. + * @param[out] outAdapters User allocated array of PalAdapter. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palEnumerateAdapters( + uint32_t* count, + PalAdapter** outAdapters); + +/** + * @brief Get information about an adapter (GPU). + * + * @param[in] adapter Adapter to query information on. + * @param[out] info Pointer to a PalAdapterInfo to fill. + * + * Thread safety: Thread safe if `info` is per thread. + * + * @since 2.0 + * @sa palEnumerateAdapters + */ +PAL_API void PAL_CALL palGetAdapterInfo( + PalAdapter* adapter, + PalAdapterInfo* info); + +/** + * @brief Get capabilites or limits about an adapter (GPU). + * + * @param[in] adapter Adapter to query capabilities on. + * @param[out] caps Pointer to a PalAdapterCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + * @sa palEnumerateAdapters + */ +PAL_API void PAL_CALL palGetAdapterCapabilities( + PalAdapter* adapter, + PalAdapterCapabilities* caps); + +/** + * @brief Get the supported features of an adapter (GPU). + * + * @param[in] adapter Adapter to query features on. + * + * @return adapter features on success or `0` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palEnumerateAdapters + */ +PAL_API PalAdapterFeatures PAL_CALL palGetAdapterFeatures(PalAdapter* adapter); + +/** + * @brief Get the highest supported shader target of an adapter (GPU). + * + * @param[in] adapter Adapter to query. + * @param[in] shaderFormat The shader format. Must have only a single bit set. + * + * @return The highest supported shader target encoded with `PAL_MAKE_SHADER_TARGET` macro + * on success otherwise `0` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palEnumerateAdapters + */ +PAL_API uint32_t PAL_CALL palGetHighestSupportedShaderTarget( + PalAdapter* adapter, + PalShaderFormats shaderFormat); + +/** + * @brief Create a device from an adapter (GPU). + * + * PAL does not enable any features by default. The created device must be destroyed using + * `palDestroyDevice()`. + * + * Every requested feature must be supported by the adapter. Use `palGetAdapterFeatures` to check + * the supported features of the adapter that can be enabled. Using a feature which is not + * supported will fail and return `PAL_RESULT_ADAPTER_FEATURE_NOT_SUPPORTED`. + * + * @param[in] adapter Adapter that creates the device. + * @param[in] features Adapter features to enable. Must be supported. + * @param[out] outDevice Pointer to a PalDevice to recieve the created device. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `adapter` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyDevice + */ +PAL_API PalResult PAL_CALL palCreateDevice( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice); + +/** + * @brief Destroy a device. + * + * All resources created with the device must be destroyed before this call. + * + * @param[in] device Pointer to the device to destroy. + * + * Thread safety: Thread safe if the adapter used to create the device is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateDevice + */ +PAL_API void PAL_CALL palDestroyDevice(PalDevice* device); + +/** + * @brief Get the native device lost reason code. + * + * This function returns the backend-specific native code for the reason the device + * was lost. Backends that do not provide explicit device lost reason codes return + * their standard device lost code. + * + * @param[in] device The device. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +PAL_API uint32_t PAL_CALL palGetDeviceLostReason(PalDevice* device); + +/** + * @brief Allocates GPU memory for the specified device. + * + * On CPU adapters, there is usually no + * `PAL_MEMORY_TYPE_GPU_ONLY` memory type available. So it uses shared memory as the vram and set + * the shared memory to the `PAL_MEMORY_TYPE_GPU_ONLY` for correctness. + * + * @param[in] device Pointer to device to allocate memory on. + * @param[in] type Memory type to allocate. Must be supported by the adapter associated with the + * device. + * @param[in] memoryMask Memory mask. Must match memory type. + * @param[in] size Number of bytes to allocate. Must not be 0. + * @param[out] outMemory Pointer to a PalMemory to recieved the allocated GPU memory. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized and + * `outMemory` is per thread. + * + * @since 2.0 + * @sa palFreeMemory + */ +PAL_API PalResult PAL_CALL palAllocateMemory( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory); + +/** + * @brief Free GPU memory allocated by palAllocateMemory. + * + * If `memory` is `nullptr`, this function will return silently. + * + * @param[in] memory Pointer to memory to free. + * + * Thread safety: Thread safe if `device` is externally synchronized and + * `outMemory` is per thread. + * + * @since 2.0 + * @sa palAllocateMemory + */ +PAL_API void PAL_CALL palFreeMemory(PalMemory* memory); + +/** + * @brief Get sampler anisotropy feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query sampler anisotropy feature capabilities on. + * @param[out] caps Pointer to a PalSamplerAnisotropyCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQuerySamplerAnisotropyCapabilities( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps); + +/** + * @brief Get multi view feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_MULTI_VIEW` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query multi view feature capabilities on. + * @param[out] caps Pointer to a PalMultiViewCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryMultiViewCapabilities( + PalDevice* device, + PalMultiViewCapabilities* caps); + +/** + * @brief Get multi viewport feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_MULTI_VIEWPORT` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query multi viewport feature capabilities on. + * @param[out] caps Pointer to a PalMultiViewportCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryMultiViewportCapabilities( + PalDevice* device, + PalMultiViewportCapabilities* caps); + +/** + * @brief Get depth stencil feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query depth stencil feature capabilities on. + * @param[out] caps Pointer to a PalDepthStencilCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryDepthStencilCapabilities( + PalDevice* device, + PalDepthStencilCapabilities* caps); + +/** + * @brief Get fragment shading rate feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query fragment shading rate feature capabilities on. + * @param[out] caps Pointer to a PalFragmentShadingRateCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryFragmentShadingRateCapabilities( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps); + +/** + * @brief Get mesh shader feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_MESH_SHADER` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query mesh shader feature capabilities on. + * @param[out] caps Pointer to a PalMeshShaderCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryMeshShaderCapabilities( + PalDevice* device, + PalMeshShaderCapabilities* caps); + +/** + * @brief Get ray tracing feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query ray tracing feature capabilities on. + * @param[out] caps Pointer to a PalRayTracingCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryRayTracingCapabilities( + PalDevice* device, + PalRayTracingCapabilities* caps); + +/** + * @brief Get descriptor indexing feature capabilites or limits about a device. + * + * `PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query descriptor indexing feature capabilities on. + * @param[out] caps Pointer to a PalDescriptorIndexingCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palQueryDescriptorIndexingCapabilities( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps); + +/** + * @brief Create a queue from a device. + * + * The created queue must be destroyed using `palDestroyQueue()`. + * + * The number of queues of each type which can be created is limited per adapter. check with + * PalAdapterCapabilities::maxComputeQueues, PalAdapterCapabilities::maxGraphicsQueues and + * PalAdapterCapabilities::maxCopyQueues respectively for the limit for each queue type. + * Creating more queues than the supported will fail and return `PAL_RESULT_OUT_OF_QUEUE`. + * + * Not all graphics queues support presentation. Create a graphics queue and then check if + * its support presentation for the provided surface. see `palCanQueuePresent()`. Any graphics + * queue supports offscreen rendering. + * + * @param[in] device Device that creates the queue. + * @param[in] type Queue type. (eg. PAL_QUEUE_TYPE_GRAPHICS). + * @param[out] outQueue Pointer to a PalQueue to recieve the created queue. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyQueue + */ +PAL_API PalResult PAL_CALL palCreateQueue( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue); + +/** + * @brief Destroy a queue. + * + * @param[in] queue Queue to destroy. + * + * Thread safety: Thread safe if the device used to create the queue is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateQueue + */ +PAL_API void PAL_CALL palDestroyQueue(PalQueue* queue); + +/** + * @brief Check if a queue is presentable to the provided window. + * + * @param[in] queue Queue to query. + * @param[in] surface Surface to check presentation support for. + * + * @return True if queue can present otherwise `PAL_FALSE` if queue can not present. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palCreateQueue + */ +PAL_API PalBool PAL_CALL palCanQueuePresent( + PalQueue* queue, + PalSurface* surface); + +/** + * @brief Blocks indefinitely until the queue becomes idle. + * + * This function blocks indefinitely until all submitted work on the queue has been completetd. + * Returns `PAL_RESULT_SUCCESS` to indicate all pending operations has been completetd. + * + * @param[in] queue Pointer to queue to wait. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `queue` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palWaitQueue(PalQueue* queue); + +/** + * @brief Returns a list of all supported formats of an adapter (GPU). + * + * This function returns the supported format with the supported image usages + * associated with the format. This is a handy way of selecting a format based on the image + * usages. Use palIsFormatSupported() to check for a specific format. + * + * Call this function first with PalFormatInfo array set to `nullptr` to get the number of formats. + * Allocate memory for the PalFormatInfo array and passed in the count and the allocated array. If + * the count of the array is less than the number of formats, PAL will write upto that limit. + * + * @param[in] adapter Adapter to query formats on. + * @param[in, out] count Capacity of the PalFormatInfo array. + * @param[out] outFormats User allocated array of PalFormatInfo. + * + * Thread safety: Thread safe if `outFormats` is per thread. + * + * @since 2.0 + * @sa palIsFormatSupported + */ +PAL_API void PAL_CALL palEnumerateFormats( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats); + +/** + * @brief Check support for a format on an adapter (GPU). + * + * This is much faster than enumerating all the formats to pick one. You directly check support + * for the format you want to use. Call palQueryFormatImageUsages() to check for supported image + * usages if format is supported. + * + * @param[in] adapter Adapter to query format on. + * @param[in] format Format to query support for. + * + * @return True if format is supported otherwise `PAL_FALSE` if not supported. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palQueryFormatImageUsages + * @sa palQueryFormatImageViewUsages + */ +PAL_API PalBool PAL_CALL palIsFormatSupported( + PalAdapter* adapter, + PalFormat format); + +/** + * @brief Checks supported image usages associated with a format. + * + * @param[in] adapter Adapter to query format on. + * @param[in] format Format to query image usages for. + * + * @return Supported image usages on success otherwise `0` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +PAL_API PalImageUsages PAL_CALL palQueryFormatImageUsages( + PalAdapter* adapter, + PalFormat format); + +/** + * @brief Checks supported sample count associated with a format. + * + * @param[in] adapter Adapter to query format on. + * @param[in] format Format to query sample count for. + * + * @return Supported sample count on success otherwise 0 on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +PAL_API PalSampleCount PAL_CALL palQueryFormatSampleCount( + PalAdapter* adapter, + PalFormat format); + +/** + * @brief Create an image. + * + * The created image must be destroyed using `palDestroyImage()`. + * + * PalImageCreateInfo::width, PalImageCreateInfo::height and PalImageCreateInfo::sampleCount + * must not be greater than the limits of the adapter used to create the device. Check + * adapter capabilities for the limits. + * + * @param[in] device Device that creates the image. + * @param[in] info Pointer to a PalImageCreateInfo struct that specifies parameters. + * @param[out] outImage Pointer to a PalImage to recieve the created image. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyImage + */ +PAL_API PalResult PAL_CALL palCreateImage( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage); + +/** + * @brief Destroy an image. + * + * @param[in] image Image to destroy. + * + * Thread safety: Thread safe if the device used to create the image is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateImage + */ +PAL_API void PAL_CALL palDestroyImage(PalImage* image); + +/** + * @brief Get information about an image. + * + * This function also supports swapchain images. + * + * @param[in] image Image to query information on. + * @param[out] info Pointer to a PalImageInfo to fill. + * + * Thread safety: Thread safe if `info` is per thread. + * + * @since 2.0 + * @sa palCreateImage + */ +PAL_API void PAL_CALL palGetImageInfo( + PalImage* image, + PalImageInfo* info); + +/** + * @brief Get memory requirements for the provided image. + * + * @param[in] image Image to query memory requirements on. + * @param[out] requirements Pointer to a PalMemoryRequirements to fill. + * + * Thread safety: Thread safe if `requirements` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetImageMemoryRequirements( + PalImage* image, + PalMemoryRequirements* requirements); + +/** + * @brief Bind an allocated memory to an image. + * + * The memory size and alignment should match the requirements of the image. + * Get the requirements with palGetImageMemoryRequirements(). + * + * @param[in] image Image to bind memory to. + * @param[in] memory Memory to bind. + * @param[in] offset Starting point within the memory. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `requirements` is per thread. + * + * @since 2.0 + * @sa palGetImageMemoryRequirements + */ +PAL_API PalResult PAL_CALL palBindImageMemory( + PalImage* image, + PalMemory* memory, + uint64_t offset); + +/** + * @brief Create an image view. + * + * The created image view must be destroyed using `palDestroyImageView()`. + * + * PalImageViewCreateInfo::type must be compatible by the type of the base image. Eg. A 2D base + * image must be have an image view of either `PAL_IMAGE_VIEW_TYPE_2D` or + * `PAL_IMAGE_VIEW_TYPE_2D_ARRAY`. + * + * `PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY` must be supported and enabled by the device + * used to create the image view if `PAL_IMAGE_VIEW_TYPE_CUBE_ARRAY` will be used. + * Otherwise behavior is undefined. + * + * @param[in] device Device that creates the image view. + * @param[in] image Image to create the image view with. + * @param[in] info Pointer to a PalImageViewCreateInfo struct that specifies parameters. + * @param[out] outImageView Pointer to a PalImageView to recieve the created image view. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyImageView + */ +PAL_API PalResult PAL_CALL palCreateImageView( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView); + +/** + * @brief Destroy an image view. + * + * @param[in] imageView Image view to destroy. + * + * Thread safety: Thread safe if the device used to create the image view is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateImageView + */ +PAL_API void PAL_CALL palDestroyImageView(PalImageView* imageView); + +/** + * @brief Create a sampler. + * + * The created sampler must be destroyed using `palDestroySampler()`. + * + * @param[in] device Device that creates the sampler. + * @param[in] info Pointer to a PalSamplerCreateInfo struct that specifies parameters. + * @param[out] outSampler Pointer to a PalSampler to recieve the created sampler. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroySampler + */ +PAL_API PalResult PAL_CALL palCreateSampler( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler); + +/** + * @brief Destroy a sampler. + * + * @param[in] sampler Sampler to destroy. + * + * Thread safety: Thread safe if the device used to create the sampler is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateSampler + */ +PAL_API void PAL_CALL palDestroySampler(PalSampler* sampler); + +/** + * @brief Create a surface for a window. + * + * The created surface must be destroyed using `palDestroySurface()`. + * + * `PAL_ADAPTER_FEATURE_SWAPCHAIN` must be supported and enabled when creating the device. + * Otherwise behavior is undefined. + * + * @param[in] device Device that creates the surface. + * @param[in] window Window to create the surface for. + * @param[in] windowInstance The instance of the window. + * @param[in] instanceType The instance type of the window. + * @param[out] outSurface Pointer to a PalSurface to recieve the created surface. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroySurface + */ +PAL_API PalResult PAL_CALL palCreateSurface( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface); + +/** + * @brief Destroy a surface. + * + * + * + * @param[in] surface Surface to destroy. + * + * Thread safety: Thread safe if the device used to create the surface is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateSurface + */ +PAL_API void PAL_CALL palDestroySurface(PalSurface* surface); + +/** + * @brief Get surface capabilites about a device. + * + * + * + * `PAL_ADAPTER_FEATURE_SWAPCHAIN` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] device Device to query surface feature capabilities on. + * @param[in] surface Surface to query capabilities. + * @param[out] caps Pointer to a PalSurfaceCapabilities to fill. + * + * Thread safety: Thread safe if `caps` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetSurfaceCapabilities( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps); + +/** + * @brief Create a swaphain. + * + * + * The created swapchain must be destroyed using `palDestroySwapchain()`. + * + * `PAL_ADAPTER_FEATURE_SWAPCHAIN` must be supported and enabled when creating the device. + * Otherwise behavior is undefined. + * + * @param[in] device Device that creates the swapchain. + * @param[in] queue Queue to create swapchain with. This must be a graphics queue. + * @param[in] surface Surface to create swapchain with. + * @param[in] info Pointer to a PalSwapchainCreateInfo struct that specifies parameters. + * @param[out] outSwapchain Pointer to a PalSwapchain to recieve the created swapchain. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroySwapchain + */ +PAL_API PalResult PAL_CALL palCreateSwapchain( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain); + +/** + * @brief Destroy a swapchain. + * + * + * + * @param[in] swapchain Swapchain to destroy. + * + * Thread safety: Thread safe if the device used to create the swapchain is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateSwapchain + */ +PAL_API void PAL_CALL palDestroySwapchain(PalSwapchain* swapchain); + +/** + * @brief Get a swapchain image from the list of images with an index. + * + * + * + * @param[in] swapchain Swapchain to get image from. + * @param[in] index Index of image in the list. Must not be greater than the image count. + * + * @return A pointer to the image on success otherwise `nullptr` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palGetNextSwapchainImage + */ +PAL_API PalImage* PAL_CALL palGetSwapchainImage( + PalSwapchain* swapchain, + uint32_t index); + +/** + * @brief Get the next available image from the swapchain image list. + * + * + * + * @param[in] swapchain Swapchain to get image index from. + * @param[in] info Pointer to a PalSwapchainNextImageInfo struct that specifies parameters. + * @param[out] outIndex Pointer to a uint32_t to recieve the next image index. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `swapchain` externally synchronized. + * + * @since 2.0 + * @sa palGetSwapchainImage + */ +PAL_API PalResult PAL_CALL palGetNextSwapchainImage( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex); + +/** + * @brief Present the swapchain. + * + * + * + * @param[in] swapchain Swapchain to present. + * @param[in] imageIndex Swapchain image index to present. + * @param[in] waitSemaphore The semaphore to wait for. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palPresentSwapchain( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore); + +/** + * @brief Resize the provided swapchain. + * + * + * + * The swapchain images must not be in use before this call. All resources (image views) that + * reference the swapchain images must be destroyed and recreated. + * + * @param[in] swapchain Swapchain to resize. + * @param[in] newWidth The new width of the swapchain. + * @param[in] newHeight The new height of the swapchain. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `swapchain` externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palResizeSwapchain( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight); + +/** + * @brief Create a shader. + * + * + * The created shader must be destroyed using `palDestroyShader()`. + * + * `PAL_ADAPTER_FEATURE_GEOMETRY_SHADER` must be supported and enabled by the device if + * `PAL_SHADER_STAGE_GEOMETRY` will be used. + * + * `PAL_ADAPTER_FEATURE_MESH_SHADER` must be supported and enabled by the device if + * `PAL_SHADER_STAGE_MESH` or `PAL_SHADER_STAGE_TASK` will be used. + * + * `PAL_ADAPTER_FEATURE_TESSELLATION_SHADER` must be supported and enabled by the device if + * `PAL_SHADER_STAGE_TESSELLATION_CONTROL` or `PAL_SHADER_STAGE_TESSELLATION_EVALUATION` will + * be used. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device if + * `PAL_SHADER_STAGE_RAYGEN` or `PAL_SHADER_STAGE_CLOSEST_HIT` or `PAL_SHADER_STAGE_ANY_HIT` or + * `PAL_SHADER_STAGE_MISS` or `PAL_SHADER_STAGE_INTERSECTION` or `PAL_SHADER_STAGE_CALLABLE` will + * be used. + * + * @param[in] device Device that creates the shader. + * @param[in] info Pointer to a PalShaderCreateInfo struct that specifies parameters. + * @param[out] outShader Pointer to a PalShader to recieve the created shader. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @note The shader entry name must not be greater than `PAL_SHADER_ENTRY_NAME_SIZE (32)`. + * + * @since 2.0 + * @sa palDestroyShader + */ +PAL_API PalResult PAL_CALL palCreateShader( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader); + +/** + * @brief Destroy a shader. + * + * + * + * @param[in] shader Shader to destroy. + * + * Thread safety: Thread safe if the device used to create the shader is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateShader + */ +PAL_API void PAL_CALL palDestroyShader(PalShader* shader); + +/** + * @brief Create a fence. + * + * + * The created fence must be destroyed using `palDestroyFence()`. + * + * @param[in] device Device that creates the fence. + * @param[in] signaled True if fence should be created signaled. If true, the fence must be reset + * before waiting for it to prevent waiting forever. + * @param[out] outFence Pointer to a PalFence to recieve the created fence. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyFence + */ +PAL_API PalResult PAL_CALL palCreateFence( + PalDevice* device, + PalBool signaled, + PalFence** outFence); + +/** + * @brief Destroy a fence. + * + * + * + * @param[in] fence Fence to destroy. + * + * Thread safety: Thread safe if the device used to create the fence is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateFence + */ +PAL_API void PAL_CALL palDestroyFence(PalFence* fence); + +/** + * @brief Wait for a fence. + * + * + * + * This function blocks for `timeout` until the fence is signaled or there is a timeout. + * Returns `PAL_RESULT_SUCCESS` or `PAL_RESULT_TIMEOUT` respectively. + * + * @param[in] fence Fence to wait for. + * @param[in] timeout Time to wait for in milliseconds. Set to `PAL_INFINITE` for indefintely. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `fence` is externally synchronized. + * + * @since 2.0 + * @sa palIsFenceSignaled + */ +PAL_API PalResult PAL_CALL palWaitFence( + PalFence* fence, + uint64_t timeout); + +/** + * @brief Reset a fence to an unsignaled state. + * + * + * + * `PAL_ADAPTER_FEATURE_FENCE_RESET` must be supported and enabled when creating the + * device. Otherwise behavior is undefined. + * + * @param[in] fence Fence to reset. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `fence` is externally synchronized. + * + * @since 2.0 + * @sa palIsFenceSignaled + */ +PAL_API PalResult PAL_CALL palResetFence(PalFence* fence); + +/** + * @brief Checks if the provided fence is in a signaled state. + * + * + * + * @param[in] fence Fence to check. + * + * @return True if signaled otherwise `PAL_FALSE`. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palResetFence + * @sa palWaitFence + */ +PAL_API PalBool PAL_CALL palIsFenceSignaled(PalFence* fence); + +/** + * @brief Create a semaphore. + * + * + * The created semaphore must be destroyed using `palDestroySemaphore()`. + * + * @param[in] device Device that creates the semaphore. + * @param[in] enableTimeline If true, `PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE` must be supported + * and enabled by the device creating the semaphore. + * @param[out] outSemaphore Pointer to a PalSemaphore to recieve the created semaphore. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroySemaphore + */ +PAL_API PalResult PAL_CALL palCreateSemaphore( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore); + +/** + * @brief Destroy a semaphore. + * + * + * + * @param[in] semaphore Semaphore to destroy. + * + * Thread safety: Thread safe if the device used to create the semaphore is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateSemaphore + */ +PAL_API void PAL_CALL palDestroySemaphore(PalSemaphore* semaphore); + +/** + * @brief Waits for a semaphore to reach the provided value. + * + * + * The provided semaphore must be a timeline semaphore. Otherwise undefined behavior. + * + * @param[in] semaphore Semaphore to wait on. + * @param[in] value Value to wait for. + * @param[in] timeout Time to wait for in milliseconds. Set to `PAL_INFINITE` for indefintely. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `semaphore` is externally synchronized. + * + * @since 2.0 + * @sa palSignalSemaphore + * @sa palGetSemaphoreValue + */ +PAL_API PalResult PAL_CALL palWaitSemaphore( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout); + +/** + * @brief Signals a semaphore from the provided value. + * + * + * The provided semaphore must be a timeline semaphore. Otherwise undefined behavior. + * + * @param[in] semaphore Semaphore to signal. + * @param[in] queue Queue used to signal the semaphore. + * @param[in] value Value to signal. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `queue` is externally synchronized. + * + * @since 2.0 + * @sa palWaitSemaphore + * @sa palGetSemaphoreValue + */ +PAL_API PalResult PAL_CALL palSignalSemaphore( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value); + +/** + * @brief Get the value of a semaphore. + * + * + * The provided semaphore must be a timeline semaphore. Otherwise undefined behavior. + * + * @param[in] semaphore Semaphore to get its value. + * @param[out] value The semaphore value. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `semaphore` is externally synchronized. + * + * @since 2.0 + * @sa palWaitSemaphore + * @sa palSignalSemaphore + */ +PAL_API PalResult PAL_CALL palGetSemaphoreValue( + PalSemaphore* semaphore, + uint64_t* value); + +/** + * @brief Create a command pool from a device. + * + * + * The created command pool must be destroyed using `palDestroyCommandPool()`. + * + * @param[in] device Device that creates the command pool. + * @param[in] queue Queue the command pool buffers will be submitted to. + * @param[out] outPool Pointer to a PalCommandPool to recieve the created command pool. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyCommandPool + */ +PAL_API PalResult PAL_CALL palCreateCommandPool( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool); + +/** + * @brief Destroy a command pool. + * + * + * All command buffers allocated from the pool must be freed before this call, + * otherwise undefined behavior. + * + * @param[in] pool Command pool to destroy. + * + * Thread safety: Thread safe if the device used to create the command pool is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateCommandPool + */ +PAL_API void PAL_CALL palDestroyCommandPool(PalCommandPool* pool); + +/** + * @brief Reset all command buffers allocated from the provided command pool. + * + * + * + * @param[in] pool Command pool to reset its command buffers. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `pool` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palResetCommandPool(PalCommandPool* pool); + +/** + * @brief Allocate a command buffer from the provided command pool. + * + * + * + * @param[in] device Device to allocate command buffer on. + * @param[in] pool Command pool to allocate command buffer from. + * @param[in] type Type of the command buffer (eg. PAL_COMMAND_BUFFER_TYPE_PRIMARY). + * @param[out] outCmdBuffer Pointer to a PalCommandBuffer to recieve the created command buffer. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` and `pool` are externally synchronized. + * + * @since 2.0 + * @sa palFreeCommandBuffer + */ +PAL_API PalResult PAL_CALL palAllocateCommandBuffer( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdbuffer); + +/** + * @brief Free an allocated command buffer. + * + * + * + * @param[in] cmdBuffer Command buffer to free. + * + * Thread safety: Thread safe if the command pool used to create the command buffer is + * externally synchronized. + * + * @since 2.0 + * @sa palAllocateCommandBuffer + */ +PAL_API void PAL_CALL palFreeCommandBuffer(PalCommandBuffer* cmdBuffer); + +/** + * @brief Reset the provided command buffer. + * + * + * + * @param[in] cmdBuffer Command buffer to reset. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palResetCommandBuffer(PalCommandBuffer* cmdBuffer); + +/** + * @brief Submit a command buffer to the provided queue for execution. + * + * The command buffer must not + * be in a recording state. + * + * @param[in] queue Queue to execute the command buffer. + * @param[in] info Pointer to a PalCommandBufferSubmitInfo struct that specifies parameters. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `queue` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palSubmitCommandBuffer( + PalQueue* queue, + PalCommandBufferSubmitInfo* info); + +/** + * @brief Begin recording commands to the provided command buffer. + * + * This function must be called + * before any other `palCmd**` function is used. + * + * @param[in] cmdBuffer Command buffer to begin recording. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + * @sa palCmdEnd + */ +PAL_API PalResult PAL_CALL palCmdBegin( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info); + +/** + * @brief End recording commands to the provided command buffer. + * + * + * + * @param[in] cmdBuffer Command buffer to begin recording. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + * @sa palCmdBegin + */ +PAL_API PalResult PAL_CALL palCmdEnd(PalCommandBuffer* cmdBuffer); + +/** + * @brief Execute a secondary command buffer within a primary command buffer. + * + * The `secondaryCmdBuffer` must + * be created with the type `PAL_COMMAND_BUFFER_TYPE_SECONDARY`. + * + * @param[in] primaryCmdBuffer Primary command buffer. Must be in recording state. + * @param[in] secondaryCmdBuffer Secondary command buffer. + * + * Thread safety: Thread safe if `primaryCmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdExecuteCommandBuffer( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer); + +/** + * @brief Set the fragment shading rate used for draw calls. + * + * + * + * `PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] state Pointer to a PalFragmentShadingRateState struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetFragmentShadingRate( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state); + +/** + * @brief Dispatch mesh shader workgroups. + * + * + * + * `PAL_ADAPTER_FEATURE_MESH_SHADER` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] groupCountX Number of mesh shader groups to dispatch on the x axis. + * @param[in] groupCountY Number of mesh shader groups to dispatch on the y axis. + * @param[in] groupCountZ Number of mesh shader groups to dispatch on the z axis. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDrawMeshTasks( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/** + * @brief Dispatch mesh shader workgroups using parameters from a buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_MESH_SHADER` and `PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH` must be supported + * and enabled by the device. Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDispatchIndirectData structs. + * @param[in] drawCount Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDrawMeshTasksIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount); + +/** + * @brief Dispatch mesh shader workgroups using parameters from buffers. + * + * + * + * `PAL_ADAPTER_FEATURE_MESH_SHADER` and `PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT` must be + * supported and enabled by the device. Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDispatchIndirectData structs. + * @param[in] countBuffer Buffer containing a single `uint32_t` specifying the number of draws. + * @param[in] maxDrawCount Maximum Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDrawMeshTasksIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + +/** + * @brief Build or update an acceleration structure. + * + * + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] info Pointer to a PalAccelerationStructureBuildInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBuildAccelerationStructure( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info); + +/** + * @brief Begin a rendering pass. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] info Pointer to a PalRenderingInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBeginRendering( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info); + +/** + * @brief End a rendering pass. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdEndRendering(PalCommandBuffer* cmdBuffer); + +/** + * @brief Copy data from one buffer to the other. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] dst Destination buffer. + * @param[in] src Source buffer. + * @param[in] copyInfo Pointer to a PalBufferCopyInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdCopyBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo); + +/** + * @brief Copy data from a buffer to an image. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] dstImage Destination image. + * @param[in] srcBuffer Source buffer. + * @param[in] copyInfo Pointer to a PalBufferImageCopyInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdCopyBufferToImage( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo); + +/** + * @brief Copy data from one image to the other. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] dst Destination image. + * @param[in] src Source image. + * @param[in] copyInfo Pointer to a PalImageCopyInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdCopyImage( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo); + +/** + * @brief Copy data from an image to a buffer. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] dstBuffer Destination buffer. + * @param[in] srcImage Source image. + * @param[in] copyInfo Pointer to a PalBufferImageCopyInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdCopyImageToBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo); + +/** + * @brief Bind a pipeline. + * + * Every pipeline knows it types which is + * set at the respective creation functions. (`palCreate**Graphics/Compute/RayTracing**Pipeline`). + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] pipeline Pipeline to bind. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBindPipeline( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline); + +/** + * @brief Set the viewport(s) used in draw commands. + * + * This always overwrites any previous + * viewports that were set since the first viewport index is always 0. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] count Capacity of the PalViewport array. + * @param[in] viewports Pointer to an array of viewports. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetViewport( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports); + +/** + * @brief Set the scissor(s) used in draw commands. + * + * This always overwrites any previous + * scissors that were set since the first scissor index is always 0. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] count Capacity of the PalRect2D array. + * @param[in] scissors Pointer to an array of scissors. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetScissors( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors); + +/** + * @brief Bind vertex buffer(s) used in draw commands. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] firstSlot Index of the first vertex buffer binding slot. + * @param[in] count Number of vertex buffers to bind. + * @param[in] buffers Pointer to an array of vertex buffers. + * @param[in] offsets Pointer to an array of offsets in bytes into each vertex buffer. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBindVertexBuffers( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets); + +/** + * @brief Bind index buffer used in draw commands. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Index buffer to bind. + * @param[in] offset Offset in bytes into the index buffer. + * @param[in] type Type of indices stored in the index buffer. (eg. `PAL_INDEX_TYPE_UINT32`). + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBindIndexBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type); + +/** + * @brief Issue a non-indexed draw command. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] vertexCount Number of vertices to draw. + * @param[in] instanceCount Number of instances to draw. + * @param[in] firstVertex Index of the first vertex to draw. + * @param[in] firstInstance Index of the first instance to draw. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + * @sa palDrawIndexed + */ +PAL_API void PAL_CALL palCmdDraw( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + +/** + * @brief Issue a non-indexed draw command using buffers. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_DRAW` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDrawIndirectData structs. + * Can be a single struct. + * @param[in] count Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palDrawIndexedIndirect + */ +PAL_API void PAL_CALL palCmdDrawIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +/** + * @brief Issue a non-indexed draw command using buffers. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDrawIndirectData structs. + * @param[in] countBuffer Buffer containing a single `uint32_t` specifying the number of draws. + * @param[in] maxDrawCount Maximum Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palCmdDrawIndexedIndirectCount + */ +PAL_API void PAL_CALL palCmdDrawIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + +/** + * @brief Issue an indexed draw command. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] indexCount Number of indices to draw. + * @param[in] instanceCount Number of instances to draw. + * @param[in] firstIndex Index of the first index to draw. + * @param[in] vertexOffset Added offset to vertex indices. + * @param[in] firstInstance Index of the first instance to draw. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + * @sa palCmdDraw + */ +PAL_API void PAL_CALL palCmdDrawIndexed( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + +/** + * @brief Issue an indexed draw command using buffers. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_DRAW` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDrawIndexedIndirectData structs. + * @param[in] count Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palCmdDrawIndirect + */ +PAL_API void PAL_CALL palCmdDrawIndexedIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +/** + * @brief Issue an indexed draw command using buffers. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing an array of PalDrawIndexedIndirectData structs. + * @param[in] countBuffer Buffer containing a single `uint32_t` specifying the number of draws. + * @param[in] maxDrawCount Maximum Number of draws to perform. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palCmdDrawIndirectCount + */ +PAL_API void PAL_CALL palCmdDrawIndexedIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + +/** + * @brief Transition an acceleration structure from one usage state to another. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * This function defines a + * dependency between `PalBarrierInfo::oldState` and `PalBarrierInfo::newState`. It ensures that + * all operations performed under the old `PalBarrierInfo::oldState` are completed and visible + * before the acceleration structure is accessed under `PalBarrierInfo::newState`. + * + * This function does not modify the acceleration structure, it only exforces execution ordering + * and acceleration structure memory visibility. + * + * A barrier is not needed between BLAS and TLAS if there dont shared any resource. If both + * builds use a different scratch buffer, no barrier is needed. + * + * Example: + * + * To make sure BLAS builds before TLAS access it and TLAS does not use scratch buffer + * whilst BLAS is buidling, we put a barrier to transition the BLAS to ensure it has finished + * building and the scratch buffer is not being used. This is expressed with + * `PalBarrierInfo::oldState` being set to `PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE` and + * `PalBarrierInfo::srcStages` being set to `PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD`. + * + * `PalBarrierInfo::newState` being `PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ` and + * `PalBarrierInfo::dstStages` being `PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD`. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] as Acceleration structure to set barrier on. + * @param[in] info Pointer to a PalBarrierInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + * @sa palCmdImageBarrier + * @sa palCmdBufferBarrier + */ +PAL_API void PAL_CALL palCmdAccelerationStructureBarrier( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info); + +/** + * @brief Transition an image from one usage state to another. + * + * This function defines a + * dependency between `PalBarrierInfo::oldState` and `PalBarrierInfo::newState`. It ensures that all + * operations performed under `PalBarrierInfo::oldState` are completed and visible before the image + * is accessed under `PalBarrierInfo::newState`. + * + * This function does not modify the image, it only exforces execution ordering and image memory + * visibility. + * + * Example: + * + * To make sure the an image is ready for presenting after a render pass, + * we put a barrier to transition the image to ensure the render pass has finished writing + * to the image. This is expressed with `PalBarrierInfo::oldState` being + * `PAL_USAGE_STATE_COLOR_ATTACHMENT` and `PalBarrierInfo::srcStages` being + * `PAL_PIPELINE_STAGE_COLOR_ATTACHMENT`. + * + * `PalBarrierInfo::newState` being `PAL_USAGE_STATE_PRESENT` and + * `PalBarrierInfo::dstStages` being `PAL_PIPELINE_STAGE_NONE`. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] image Image to set barrier on. + * @param[in] subresourceRange Subresource range of the image. + * @param[in] info Pointer to a PalBarrierInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + * @sa palCmdAccelerationStructureBarrier + * @sa palCmdBufferBarrier + */ +PAL_API void PAL_CALL palCmdImageBarrier( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info); + +/** + * @brief Transition a buffer from one usage state to another. + * + * This function defines a + * dependency between `PalBarrierInfo::oldState` and `PalBarrierInfo::newState`. It ensures that all + * operations performed under `PalBarrierInfo::oldState` are completed and visible before the buffer + * is accessed under `PalBarrierInfo::newState`. + * + * This function does not modify the buffer, it only exforces execution ordering and buffer memory + * visibility. + * + * Example: + * + * To read back data from a buffer that will be written to by a shader, + * we put a barrier to transition the buffer to ensure the shader has finished writing to the + * buffer. This is expressed with `PalBarrierInfo::oldState` being `PAL_USAGE_STATE_SHADER_WRITE` + * and `PalBarrierInfo::srcStages` being the shader stage that wrote to the buffer + * (eg. `PAL_PIPELINE_STAGE_COMPUTE_SHADER`). + * + * `PalBarrierInfo::newState` being `PAL_USAGE_STATE_TRANSFER_READ` and + * `PalBarrierInfo::dstStages` being `PAL_PIPELINE_STAGE_TRANSFER`. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer to set barrier on. + * @param[in] info Pointer to a PalBarrierInfo struct that specifies parameters. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + * @sa palCmdAccelerationStructureBarrier + * @sa palCmdImageBarrier + */ +PAL_API void PAL_CALL palCmdBufferBarrier( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info); + +/** + * @brief Dispatch compute shader workgroups. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] groupCountX Number of compute shader groups to dispatch on the x axis. + * @param[in] groupCountY Number of compute shader groups to dispatch on the y axis. + * @param[in] groupCountZ Number of compute shader groups to dispatch on the z axis. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDispatch( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/** + * @brief Dispatch compute shader workgroups with base offset. + * + * + * + * `PAL_ADAPTER_FEATURE_DISPATCH_BASE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] baseGroupX Base group offset on the x axis. + * @param[in] baseGroupY Base group offset on the y axis. + * @param[in] baseGroupZ Base group offset on the z axis. + * @param[in] groupCountX Number of compute shader groups to dispatch on the x axis. + * @param[in] groupCountY Number of compute shader groups to dispatch on the y axis. + * @param[in] groupCountZ Number of compute shader groups to dispatch on the z axis. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDispatchBase( + PalCommandBuffer* cmdBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +/** + * @brief Dispatch compute shader workgroups using parameters from a buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_DISPATCH` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] buffer Buffer containing the PalDispatchIndirectData struct. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + * @sa palBuildWorkGroupInfo + */ +PAL_API void PAL_CALL palCmdDispatchIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer); + +/** + * @brief Dispatch rays. + * + * + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] sbt The shader binding table to use. + * @param[in] raygenIndex Index of the raygen shader to execute. + * @param[in] width Number of rays to trace on the x axis. + * @param[in] height Number of rays to trace on the y axis. + * @param[in] depth Number of rays to trace on the z axis. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdTraceRays( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth); + +/** + * @brief Dispatch rays using parameters from a buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] raygenIndex Index of the raygen shader to execute. + * @param[in] sbt The shader binding table to use. + * @param[in] buffer Buffer containing the PalDispatchIndirectData struct. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note The argument buffer memory must not be `PAL_MEMORY_TYPE_CPU_UPLOAD`. The implementation + * internally copies the data into a GPU buffer for execution. + * + * @note A pipeline must be bound before this call. + * @note The buffer must be created with `PAL_BUFFER_USAGE_INDIRECT` usage. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdTraceRaysIndirect( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer); + +/** + * @brief Bind a descriptor set to the provided command buffer. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] setIndex Index of the descriptor set to bind. + * @param[in] set Descriptor set to bind. Must be compatible with `layout`. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdBindDescriptorSet( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set); + +/** + * @brief Update push constant data for the provided command buffer. + * + * + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] offset Offset in bytes into the push constant range. + * @param[in] size Size of `value` in bytes. + * @param[in] value Pointer to the push constant range data to write. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @note A pipeline must be bound before this call. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdPushConstants( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value); + +/** + * @brief Set the cull mode for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] cullMode Cull mode to set. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetCullMode( + PalCommandBuffer* cmdBuffer, + PalCullMode cullMode); + +/** + * @brief Set the front face for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] frontFace Front face to set. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetFrontFace( + PalCommandBuffer* cmdBuffer, + PalFrontFace frontFace); + +/** + * @brief Set the primitive topology for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] topology Topology to set. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetPrimitiveTopology( + PalCommandBuffer* cmdBuffer, + PalPrimitiveTopology topology); + +/** + * @brief Set depth test enable for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] enable True to enable. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetDepthTestEnable( + PalCommandBuffer* cmdBuffer, + PalBool enable); + +/** + * @brief Set depth write enable for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] enable True to enable. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetDepthWriteEnable( + PalCommandBuffer* cmdBuffer, + PalBool enable); + +/** + * @brief Set depth stencil operation for the provided command buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP` must be supported and enabled by the device. + * + * @param[in] cmdBuffer Command buffer being recorded. + * @param[in] faceMask Bitmask specifying faces to apply the stencil to. + * @param[in] failOp Stencil operation to perform when stencil fails. + * @param[in] passOp Stencil operation to perform when stencil and depth passes. + * @param[in] depthFailOp Stencil operation to perform when stencil passes but depth fails. + * @param[in] compareOp Compare operation for stencil tests. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palCmdSetStencilOp( + PalCommandBuffer* cmdBuffer, + PalStencilFaceFlags faceMask, + PalStencilOp failOp, + PalStencilOp passOp, + PalStencilOp depthFailOp, + PalCompareOp compareOp); + +/** + * @brief Create an acceleration structure. + * + * + * The created acceleration structure must be destroyed using `palDestroyAccelerationStructure()`. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] device Device that creates the acceleration structure. + * @param[in] info Pointer to a PalAccelerationStructureCreateInfo struct that specifies parameters + * @param[out] outAs Pointer to a PalAccelerationStructure to recieve the created acceleration + * structure. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyAccelerationStructure + */ +PAL_API PalResult PAL_CALL palCreateAccelerationstructure( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs); + +/** + * @brief Destroy an acceleration structure. + * + * + * + * @param[in] as Acceleration structure to destroy. + * + * Thread safety: Thread safe if the device used to create the acceleration structure is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateAccelerationstructure + */ +PAL_API void PAL_CALL palDestroyAccelerationStructure(PalAccelerationStructure* as); + +/** + * @brief Get the build size of an acceleration structure. + * + * + * PalAccelerationStructureBuildInfo::dst, PalAccelerationStructureBuildInfo::scratchBufferAddress + * and PalAccelerationStructureBuildInfo::src must be set to `nullptr`. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] device Device to query. + * @param[in] info Pointer to a PalAccelerationStructureBuildInfo struct that specifies parameters. + * @param[out] size Pointer to a PalAccelerationStructureBuildSize to recieve the build size. + * + * Thread safety: Thread safe if `cmdBuffer` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetAccelerationStructureBuildSize( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size); + +/** + * @brief Create a buffer. + * + * + * The created buffer must be destroyed using `palDestroyBuffer()`. + * + * `PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS` must be supported and enabled by the devic. + * Otherwise behavior is undefined. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device if + * `PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE` or `PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH` + * or `PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_READ_ONLY_INPUT` will be used. + * + * `PAL_BUFFER_USAGE_INDIRECT` must be supported and enabled by the device if the buffer will be + * used as an indirect buffer. + * + * @param[in] device Device that creates the buffer. + * @param[in] info Pointer to a PalBufferCreateInfo struct that specifies parameters. + * @param[out] outBuffer Pointer to a PalBuffer to recieve the created buffer. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyBuffer + */ +PAL_API PalResult PAL_CALL palCreateBuffer( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer); + +/** + * @brief Destroy a buffer. + * + * + * + * @param[in] buffer buffer to destroy. + * + * Thread safety: Thread safe if the device used to create the buffer is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateBuffer + */ +PAL_API void PAL_CALL palDestroyBuffer(PalBuffer* buffer); + +/** + * @brief Get memory requirements for the provided buffer. + * + * + * + * @param[in] buffer Buffer to query memory requirements on. + * @param[out] requirements Pointer to a PalMemoryRequirements to fill. + * + * Thread safety: Thread safe if `requirements` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetBufferMemoryRequirements( + PalBuffer* buffer, + PalMemoryRequirements* requirements); + +/** + * @brief Compute size for an acceleration structure instance buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * This does not allocate memory for the buffer. This function must is required for all + * acceleration structure instance buffers. + * + * @param[in] device The device to use. + * @param[in] instanceCount Number of instances the instance buffer will hold. + * @param[out] outSize Pointer to a uint64_t to recieve the required size. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palWriteInstanceStaging + */ +PAL_API void PAL_CALL palComputeInstanceStagingSize( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize); + +/** + * @brief Compute requirements for an image staging buffer. + * + * This does not allocate memory + * for the buffer. This function is required for all image copy staging buffers. + * + * `PalBufferImageCopyInfo::bufferRowLength` and `PalBufferImageCopyInfo::bufferImageHeight` + * are hints. The driver might used it defaults if the requested is not supported. After this call, + * set those values to the required ones from `requirements`. + * If the driver supports the proivded, the values will be the same. + * + * @param[in] device The device to use. + * @param[in] imageFormat Destination image format. + * @param[in] copyInfo Pointer to a PalBufferImageCopyInfo struct that specifies parameters. + * @param[out] requirements Pointer to a PalImageStagingRequirements to recieve the requirements + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palWriteImageStaging + */ +PAL_API void PAL_CALL palComputeImageStagingRequirements( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements); + +/** + * @brief Write data to an instance staging buffer. + * + * + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] device The device to use. + * @param[in] instanceCount Number of instances. + * @param[in] instances Array of PalAccelerationStructureInstance struct to write. + * @param[out] ptr Pointer to the CPU visible memory. Must be mapped. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palComputeInstanceStagingSize + */ +PAL_API void PAL_CALL palWriteInstanceStaging( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr); + +/** + * @brief Write data to an image staging buffer. + * + * + * + * @param[in] device The device to use. + * @param[in] imageFormat Destination image format. + * @param[in] copyInfo Pointer to a PalBufferImageCopyInfo struct that specifies parameters. + * @param[out] srcData Pointer to the CPU visible memory with the data. + * @param[out] ptr Pointer to the CPU visible memory. Must be mapped. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palComputeImageStagingRequirements + */ +PAL_API void PAL_CALL palWriteImageStaging( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr); + +/** + * @brief Bind an allocated memory to a buffer. + * + * + * The memory size and alignment should match the requirements of the buffer. + * Get the requirements with palGetBufferMemoryRequirements(). + * + * @param[in] buffer Buffer to bind memory to. + * @param[in] memory Memory to bind. + * @param[in] offset Starting point within the memory. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `requirements` is per thread. + * + * @since 2.0 + * @sa palGetBufferMemoryRequirements + */ +PAL_API PalResult PAL_CALL palBindBufferMemory( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset); + +/** + * @brief Maps buffer to CPU visible address space. + * + * The buffer must have a valid + * memory bound to it before this call. + * + * Only `PAL_MEMORY_TYPE_CPU_UPLOAD` and `PAL_MEMORY_TYPE_CPU_READBACK` can be mapped to + * CPU visible space. Mapping `PAL_MEMORY_TYPE_GPU_ONLY` will fail and return + * `PAL_RESULT_MEMORY_MAP_FAILED`. + * + * @param[in] buffer Pointer to buffer to map. Memory must be bound. + * @param[in] offset Starting point within the buffer. + * @param[in] size Number of bytes to map from the offset. `offset + size` must not be + * greater than buffer size. + * @param[out] outPtr Pointer to a void* to recieved the mapped memory. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `buffer` is externally synchronized. + * Mapping with different offsets into the same buffer is thread safe as long as `buffer` + * is externally synchronized. + * + * @since 2.0 + * @sa palUnmapBuffer + */ +PAL_API PalResult PAL_CALL palMapBuffer( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr); + +/** + * @brief Unmap buffer from CPU visible address space. + * + * The buffer must be mapped + * before this call. After this call, the CPU pointer must not be used anymore. + * + * @param[in] buffer Pointer to buffer to unmap. + * + * Thread safety: Thread safe if `buffer` is externally synchronized. + * + * @since 2.0 + * @sa palMapBuffer + */ +PAL_API void PAL_CALL palUnmapBuffer(PalBuffer* buffer); + +/** + * @brief Get the device address of the provided buffer. + * + * Buffer must have + * `PAL_BUFFER_USAGE_DEVICE_ADDRESS` usage flag. + * + * @param[in] buffer Buffer to get its device address. + * + * @return Buffer device address on success or `0` on failure. + * + * Thread safety: Thread safe if `buffer` is per thread. + * + * @since 2.0 + */ +PAL_API PalDeviceAddress PAL_CALL palGetBufferDeviceAddress(PalBuffer* buffer); + +/** + * @brief Create a descriptor set layout that defines the bindings used by descriptor sets. + * + * + * The created descriptor set layout must be destroyed using `palDestroyDescriptorSetLayout()`. + * + * This defines the layout, ordering and the number of descriptors a descriptor set uses. + * + * The layouts should reflect the exact layout of the shaders. Eg. + * descriptorBindings[2] = { sampler, sampled image } is different from + * descriptorBindings[2] = { sampled image, sampler }. The ordering must be correct. + * + * @param[in] device Device that creates the descriptor set layout. + * @param[in] info Pointer to a PalDescriptorSetLayoutCreateInfo struct that specifies parameters. + * @param[out] outLayout Pointer to a PalDescriptorSetLayout to recieve the created layout. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyDescriptorSetLayout + */ +PAL_API PalResult PAL_CALL palCreateDescriptorSetLayout( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout); + +/** + * @brief Destroy a descriptor set layout. + * + * + * + * @param[in] layout Descriptor set layout to destroy. + * + * Thread safety: Thread safe if the device used to create the descriptor set layout is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateDescriptorSetLayout + */ +PAL_API void PAL_CALL palDestroyDescriptorSetLayout(PalDescriptorSetLayout* layout); + +/** + * @brief Create a descriptor pool to allocate descriptor sets. + * + * + * The created descriptor pool must be destroyed using `palDestroyDescriptorPool()`. + * + * @param[in] device Device that creates the descriptor pool. + * @param[in] info Pointer to a PalDescriptorPoolCreateInfo struct that specifies parameters. + * @param[out] outPool Pointer to a PalDescriptorPool to recieve the created descriptor pool. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyDescriptorPool + */ +PAL_API PalResult PAL_CALL palCreateDescriptorPool( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool); + +/** + * @brief Destroy a descriptor pool. + * + * + * + * @param[in] pool Descriptor pool to destroy. + * + * Thread safety: Thread safe if the device used to create the descriptor pool is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateDescriptorPool + */ +PAL_API void PAL_CALL palDestroyDescriptorPool(PalDescriptorPool* pool); + +/** + * @brief Reset the provided descriptor pool. This resets all allocated descriptor sets. + * + * + * + * @param[in] pool Descriptor pool to reset. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `pool` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palResetDescriptorPool(PalDescriptorPool* pool); + +/** + * @brief Allocate a descriptor set from the provided descriptor pool. + * + * The descriptor set will be + * allocated uninitialized therefore update it before use except the case where descriptor + * indexing is enabled. + * + * `pool` and `layout` must either be created with descriptor indexing enabled or not. Any other + * pair will fail and return `PAL_RESULT_INVALID_OPERATION`. + * + * @param[in] device Device to allocate descriptor set on. + * @param[in] pool Descriptor pool to allocate descriptor set from. + * @param[in] layout Descriptor set layout that defines the bindings. + * @param[out] outSet Pointer to a PalDescriptorSet to recieve the created descriptor set. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` and `pool` are externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palAllocateDescriptorSet( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet); + +/** + * @brief Update a descriptor set with descriptors (resources). + * + * + * + * If the write info has no valid resource handle, then `PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS` + * must be supported and enabled when creating the device. Otherwise behavior is undefined. + * + * @param[in] device The Device. Must match the one used to allocate descriptor set. + * @param[in] count Capacity of the PalDescriptorSetWriteInfo array. + * @param[in] infos Array of PalDescriptorSetWriteInfo to write. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palUpdateDescriptorSet( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos); + +/** + * @brief Create a pipeline layout. This defines the descriptor set interfaces and push + * constant ranges. + * + * + * The created pipeline layout must be destroyed using `palDestroyPipelineLayout()`. + * + * @param[in] device Device that creates the pipeline layout. + * @param[in] info Pointer to a PalPipelineLayoutCreateInfo struct that specifies parameters. + * @param[out] outLayout Pointer to a PalPipelineLayout to recieve the created pipeline layout. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyPipelineLayout + */ +PAL_API PalResult PAL_CALL palCreatePipelineLayout( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout); + +/** + * @brief Destroy a pipeline layout. + * + * + * + * @param[in] layout Pipeline layout to destroy. + * + * Thread safety: Thread safe if the device used to create the pipeline layout is + * externally synchronized. + * + * @since 2.0 + * @sa palCreatePipelineLayout + */ +PAL_API void PAL_CALL palDestroyPipelineLayout(PalPipelineLayout* layout); + +/** + * @brief Create a graphics pipeline. + * + * + * The created pipeline must be destroyed using `palDestroyPipeline()`. + * + * @param[in] device Device that creates the graphics pipeline. + * @param[in] info Pointer to a PalGraphicsPipelineCreateInfo struct that specifies parameters. + * @param[out] outPipeline Pointer to a PalPipeline to recieve the created buffer. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @since 2.0 + * @sa palDestroyPipeline + */ +PAL_API PalResult PAL_CALL palCreateGraphicsPipeline( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline); + +/** + * @brief Create a compute pipeline. + * + * + * The created pipeline must be destroyed using `palDestroyPipeline()`. + * + * @param[in] device Device that creates the compute pipeline. + * @param[in] info Pointer to a PalComputePipelineCreateInfo struct that specifies parameters. + * @param[out] outPipeline Pointer to a PalPipeline to recieve the created buffer. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @note The first entry of the compute shader will be used. + * + * @since 2.0 + * @sa palDestroyPipeline + */ +PAL_API PalResult PAL_CALL palCreateComputePipeline( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline); + +/** + * @brief Create a ray tracing pipeline. + * + * + * The created pipeline must be destroyed using `palDestroyPipeline()`. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * @param[in] device Device that creates the ray tracing pipeline. + * @param[in] info Pointer to a PalRayTracingPipelineCreateInfo struct that specifies parameters. + * @param[out] outPipeline Pointer to a PalPipeline to recieve the created buffer. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @note The shader group array must be in this order [raygen][miss][hitgroup][callable]. + * + * @since 2.0 + * @sa palDestroyPipeline + */ +PAL_API PalResult PAL_CALL palCreateRayTracingPipeline( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline); + +/** + * @brief Destroy a pipeline. + * + * + * + * @param[in] pipeline Pipeline to destroy. + * + * Thread safety: Thread safe if the device used to create the pipeline is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateGraphicsPipeline + * @sa palCreateComputePipeline + * @sa palCreateRayTracingPipeline + */ +PAL_API void PAL_CALL palDestroyPipeline(PalPipeline* pipeline); + +/** + * @brief Create a shader binding table. + * + * + * The created shader binding table must be destroyed using `palDestroyShaderBindingTable()`. + * + * `PAL_ADAPTER_FEATURE_RAY_TRACING` must be supported and enabled by the device. + * Otherwise behavior is undefined. + * + * PalShaderBindingTableCreateInfo::recordCount must match the shader group count of + * PalShaderBindingTableCreateInfo::rayTracingPipeline. + * + * @param[in] device Device that creates the shader binding table. + * @param[in] info Pointer to a PalShaderBindingTableCreateInfo struct that specifies parameters. + * @param[out] outSbt Pointer to a PalShaderBindingTable to recieve the created shader binding + * table. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe if `device` is externally synchronized. + * + * @note The records array must be in this order [raygen][miss][hitgroup][callable]. + * + * @since 2.0 + * @sa palDestroyShaderBindingTable + */ +PAL_API PalResult PAL_CALL palCreateShaderBindingTable( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt); + +/** + * @brief Destroy a shader binding table. + * + * + * + * @param[in] sbt Shader binding table to destroy. + * + * Thread safety: Thread safe if the device used to create the shader binding table is + * externally synchronized. + * + * @since 2.0 + * @sa palCreateShaderBindingTable + */ +PAL_API void PAL_CALL palDestroyShaderBindingTable(PalShaderBindingTable* sbt); + +/** + * @brief Update a shader binding table record payloads. + * + * + * + * This call does not update shader handles. It only updates the payload associated + * with the record. PalShaderBindingTableRecordInfo::groupIndex is the index into + * the shader groups used to create the ray tracing pipeline. + * + * @param[in] sbt The shader binding table to update. + * @param[in] count Capacity of the PalShaderBindingTableRecordInfo array. + * @param[in] infos Array of PalShaderBindingTableRecordInfo to update. + * + * Thread safety: Thread safe if `sbt` is externally synchronized. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palUpdateShaderBindingTable( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos); + +/** + * @brief Build work group info(s) from work inputs specified in pixels, vertices etc. + * + * Call this function first with PalWorkGroupInfo array set to `nullptr` to get the number of work + * group infos. Allocate memory for the PalWorkGroupInfo array and passed in the count and the + * allocated array. If the count of the array is less than the number of work group infos, PAL will + * write upto that limit. + * + * If the count is 0 and the PalWorkGroupInfo array is `nullptr`, the function fails + * and returns `PAL_FALSE`. + * + * This function works the maths for how many work groups to dispatch in each axis and how many + * times it needs to be dispatch in order for the work to be done. It works well with + * palCmdDispatchBase() since its also gives the base for each work group. + * + * @param[in] data Pointer to a PalWorkGroupBuildData with parameters. + * @param[in, out] count Capacity of the PalWorkGroupInfo array. + * @param[out] infos Pointer to an Array of PalWorkGroupInfo. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palCmdDrawMeshTasks + * @sa palCmdDrawMeshTasksIndirect + * @sa palCmdDrawMeshTasksIndirectCount + * @sa palCmdDispatch + * @sa palCmdDispatchBase + */ +PAL_API void PAL_CALL palBuildWorkGroupInfo( + const PalWorkGroupBuildData* data, + uint32_t* count, + PalWorkGroupInfo* info); + +/** + * @brief Check if a constant is supported in a mask. + * + * This function is used to check all masks in `supported_**` format in most of the capabilities + * query structs. + * + * Example: + * + * To check if `PAL_PRESENT_MODE_IMMEDIATE` is supported after querying `PalSurfaceCapabilities` + * capabilities of a surface, PalSurfaceCapabilities::supportedPresentModes should be the `mask` + * parameter and `PAL_PRESENT_MODE_IMMEDIATE` as the value parameter. + * + * @param[in] mask The supported mask. + * @param[in, out] value The value to check in the supported mask. + * + * @return `PAL_TRUE` on success otherwise `PAL_FALSE`. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +static inline PalBool PAL_CALL palIsSupported( + uint32_t mask, + uint32_t value) +{ + return (mask & (1U << value)) != 0; +} + +/** @} */ + +#endif // _PAL_GRAPHICS_H diff --git a/include/pal2/pal_opengl.h b/include/pal2/pal_opengl.h new file mode 100644 index 00000000..20b26acb --- /dev/null +++ b/include/pal2/pal_opengl.h @@ -0,0 +1,453 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_opengl Opengl + * @ingroup pal_opengl + * @{ + */ + +#ifndef _PAL_OPENGL_H +#define _PAL_OPENGL_H + +#include "pal_core.h" + +// Opengl API entry +#ifdef _WIN32 +#define PAL_GL_APIENTRY __stdcall +#else +#define PAL_GL_APIENTRY +#endif // _WIN32 + +#define PAL_GL_VENDOR_NAME_SIZE 32 +#define PAL_GL_GRAPHICS_CARD_NAME_SIZE 64 +#define PAL_GL_VERSION_NAME_SIZE 64 + +#define PAL_GL_EXTENSION_CREATE_CONTEXT (1ULL << 0) +#define PAL_GL_EXTENSION_CONTEXT_PROFILE (1ULL << 1) +#define PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2 (1ULL << 2) +#define PAL_GL_EXTENSION_ROBUSTNESS (1ULL << 3) +#define PAL_GL_EXTENSION_NO_ERROR (1ULL << 4) +#define PAL_GL_EXTENSION_PIXEL_FORMAT (1ULL << 5) +#define PAL_GL_EXTENSION_MULTISAMPLE (1ULL << 6) +#define PAL_GL_EXTENSION_SWAP_CONTROL (1ULL << 7) +#define PAL_GL_EXTENSION_FLUSH_CONTROL (1ULL << 8) +#define PAL_GL_EXTENSION_COLORSPACE_SRGB (1ULL << 9) + +#define PAL_GL_PROFILE_NONE 0 +#define PAL_GL_PROFILE_CORE 1 +#define PAL_GL_PROFILE_COMPATIBILITY 2 +#define PAL_GL_PROFILE_ES 3 +#define PAL_GL_PROFILE_COUNT 4 + +#define PAL_GL_CONTEXT_RESET_NONE 0 +#define PAL_GL_CONTEXT_RESET_NO_NOTIFICATION 1 +#define PAL_GL_CONTEXT_RESET_LOSE_CONTEXT 2 +#define PAL_GL_CONTEXT_RESET_COUNT 3 + +#define PAL_GL_RELEASE_BEHAVIOR_NONE 0 +#define PAL_GL_RELEASE_BEHAVIOR_FLUSH 1 +#define PAL_GL_RELEASE_BEHAVIOR_COUNT 2 + +#define PAL_GL_BACKEND_EGL 0 +#define PAL_GL_BACKEND_GLX 1 +#define PAL_GL_BACKEND_WGL 2 +#define PAL_GL_BACKEND_COUNT 3 + +#define PAL_GL_API_OPENGL 0 +#define PAL_GL_API_OPENGL_ES 1 +#define PAL_GL_API_COUNT 2 + +/** + * @struct PalGLContext + * @brief Opaque handle to an opengl context. + * + * @since 2.0 + */ +typedef struct PalGLContext PalGLContext; + +/** + * @typedef PalGLExtensions + * @brief Opengl system extensions. + * + * All opengl extensions follow the format `PAL_GL_EXTENSION_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint64_t PalGLExtensions; + +/** + * @typedef PalGLProfile + * @brief Opengl context creation profiles. This is not a bitmask. + * + * All opengl profiles follow the format `PAL_GL_PROFILE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGLProfile; + +/** + * @typedef PalGLContextReset + * @brief Opengl context reset behavior. This is not a bitmask. + * + * All context reset behavior follow the format `PAL_GL_CONTEXT_RESET_**` + * for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGLContextReset; + +/** + * @typedef PalGLReleaseBehavior + * @brief Opengl context release behavior. This is not a bitmask. + * + * All opengl context release behavior follow the format + * `PAL_GL_RELEASE_BEHAVIOR_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGLReleaseBehavior; + +/** + * @typedef PalGLBackend + * @brief Opengl backend. This is not a bitmask. + * + * All opengl backends follow the format `PAL_GL_BACKEND_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGLBackend; + +/** + * @typedef PalGLAPI + * @brief Opengl api. This is not a bitmask. + * + * All opengl apis follow the format `PAL_GL_API_**` for consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalGLAPI; + +/** + * @struct PalGLInfo + * @brief Information about the opengl driver. + * + * @since 2.0 + */ +typedef struct { + PalGLExtensions extensions; /**< Supported extensions.*/ + uint32_t major; /**< Version major.*/ + uint32_t minor; /**< Version minor.*/ + PalGLBackend backend; /**< (eg. `PAL_GL_BACKEND_WGL`).*/ + PalGLAPI api; /**< (eg. `PAL_GL_API_OPENGL_ES`).*/ + char vendor[PAL_GL_VENDOR_NAME_SIZE]; /**< Graphics card vendor name.*/ + char graphicsCard[PAL_GL_GRAPHICS_CARD_NAME_SIZE]; /**< Graphics card name.*/ + char version[PAL_GL_VERSION_NAME_SIZE]; /**< Graphics card version string.*/ +} PalGLInfo; + +/** + * @struct PalGLFBConfig + * @brief Information about an opengl framebuffer. + * + * @since 2.0 + */ +typedef struct { + PalBool doubleBuffer; /**< If `PAL_TRUE` double buffering is supported.*/ + PalBool stereo; /**< If `PAL_TRUE` stereo is supported.*/ + PalBool sRGB; /**< If `PAL_TRUE` SRGB colorspace is supported.*/ + uint16_t index; /**< Driver index or id.*/ + uint16_t redBits; /**< Number of bits in the red channel.*/ + uint16_t greenBits; /**< Number of bits in the green channel.*/ + uint16_t blueBits; /**< Number of bits in the blue channel.*/ + uint16_t alphaBits; /**< Number of bits in the alpha channel.*/ + uint16_t depthBits; /**< Number of depth buffer bits.*/ + uint16_t stencilBits; /**< Number of stencil buffer bits.*/ + uint16_t samples; /**< Number of samples.*/ +} PalGLFBConfig; + +/** + * @struct PalGLWindow + * @brief Information about an opengl window. + * + * This can be allocated statically or dynamically since its used for + * holding native handles. The handles will not be copied. + * + * @since 2.0 + */ +typedef struct { + void* instance; /**< (HINSTANCE on Win32 or wl_display on Wayland)*/ + void* window; /**< (egl_wl_window on Wayland)*/ +} PalGLWindow; + +/** + * @struct PalGLContextCreateInfo + * @brief Creation parameters for an opengl context. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const PalGLWindow* window; /**< Window to create context for.*/ + const PalGLFBConfig* fbConfig; /**< The framebuffer config to use.*/ + PalGLContext* shareContext; /**< Can be `nullptr`.*/ + PalGLProfile profile; /**< (eg. `PAL_GL_PROFILE_CORE`).*/ + PalGLContextReset reset; /**< (eg. `PAL_GL_CONTEXT_RESET_LOSE_CONTEXT`).*/ + PalGLReleaseBehavior release; /**< (eg. `PAL_GL_RELEASE_BEHAVIOR_FLUSH`).*/ + PalBool forward; /**< Create a forward compatible context.*/ + PalBool noError; /**< Create a no error context.*/ + PalBool debug; /**< Create a debug context.*/ + uint32_t major; /**< Must not be greater than what the driver supports.*/ + uint32_t minor; /**< Must not be greater than what the driver supports.*/ +} PalGLContextCreateInfo; + +/** + * @brief Initialize the opengl system. + * + * This must be called before any opengl function. Call `palGetSupportedGLAPIs()` to check + * if `api` is supported on `instance`. + * + * The allocator will not not copied, therefore the pointer must remain valid + * until the opengl system is shutdown. + * + * `instance` must not be `nullptr` and will not be freed by the opengl system. It must be valid + * until palShutdownGL() is called. `Linux`: This is the Display associated with the connection. + * `Windows`: This is the HINSTANCE of the process. + * + * @param[in] api The api to use. (eg. `PAL_GL_API_OPENGL`). + * @param[in] instance The instance the opengl system will be tied to (eg. XDisplay). + * @param[in] allocator Optional user-provided allocator. Set to `nullptr` to use default. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palShutdownGL + */ +PAL_API PalResult PAL_CALL palInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator); + +/** + * @brief Shutdown the opengl system. + * + * If the opengl system has not been initialized, the function returns silently. + * All created contexts must be destroyed before this call. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palInitGL + */ +PAL_API void PAL_CALL palShutdownGL(); + +/** + * @brief Get information about the opengl driver. + * + * The opengl system must be initialized before this call. The returned + * PalGLInfo pointer must not be freed. + * + * @return A pointer to a PalGLInfo on success or `nullptr` on failure. + * + * Thread safety: Thread-safe. + * + * @since 2.0 + */ +PAL_API const PalGLInfo* PAL_CALL palGetGLInfo(); + +/** + * @brief Return a list of all supported PalGLFBConfig on the provided window. + * + * The opengl system must be initialized before this call. + * + * Call this function first with PalGLFBConfig array set to `nullptr` to get the + * number of supported PalGLFBConfig. Allocate memory for the PalGLFBConfig + * array and passed in the count and the allocated array. If the count of the + * array is less than the number of supported PalGLFBConfigs, PAL will write + * upto that limit. + * + * @param[in, out] count Capacity of the PalGLFBConfig array. + * @param[out] configs User allocated array of PalGLFBConfig. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palInitGL + */ +PAL_API PalResult PAL_CALL palEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs); + +/** + * @brief Get the closest PalGLFBConfig from the array of PalGLFBConfig with a + * desired PalGLFBConfig. + * + * The opengl system must be initialized before this call. + * This function uses missing and score system to get the closest PalGLFBConfig. + * + * @param[in] configs Pointer to the array of PalGLFBConfig. + * @param[in] count Capacity of the PalGLFBConfig array. + * @param[in] desired The desired PalGLFBConfig. + * + * @return The closest PalGLFBConfig on success or `nullptr` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + */ +PAL_API const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( + PalGLFBConfig* configs, + uint32_t count, + const PalGLFBConfig* desired); + +/** + * @brief Creates an opengl context. + * + * The opengl system must be initialized before this call. The created context + * will not be made current. + * + * The provided PalGLFBConfig must be the same as the one used to create the + * window. Once set, it cannot be changed. To change it, you must destroy the + * window and recreate it. + * + * On Wayland: PalGLContextCreateInfo::PalGLWindow::window is the wl_egl_window + * not the wl_surface. + * + * @param[in] info Pointer to a PalGLContextCreateInfo struct that specifies parameters. + * @param[out] outContext Pointer to a PalGLContext to recieve the created context. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palDestroyGLContext + */ +PAL_API PalResult PAL_CALL palCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext); + +/** + * @brief Destroy the provided opengl context. + * + * The opengl system must be initialized before this call. + * The context must not be current in any thread before this call. + * + * @param[in] context Pointer to the context to destroy. + * + * Thread safety: Thread safe if the `context` is per thread. + * + * @since 2.0 + * @sa palCreateGLContext + */ +PAL_API void PAL_CALL palDestroyGLContext(PalGLContext* context); + +/** + * @brief Make the provided context current on the calling thread. + * + * The opengl system must be initialized before this call. + * + * The opengl window must have the same PalGLFBConfig used to create the + * context. If the PalGLFBConfig of the opengl window is not the same as the one + * used to create the context, this function fails and returns + * `PAL_RESULT_INVALID_GL_WINDOW`. + * + * @param[in] glWindow Pointer to the opengl window. + * @param[in] context Pointer to the context to make current. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread safe, but only one thread may have the + * current context at a time. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palMakeContextCurrent( + PalGLWindow* glWindow, + PalGLContext* context); + +/** + * @brief Get the pointer to a named opengl function. + * + * The opengl system must be initialized before this call. + * + * @param[in] name UTF-8 string for the function name. + * + * @return the pointer to the function on success or `nullptr` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palInitGL + */ +PAL_API void* PAL_CALL palGetGLProcAddress(const char* name); + +/** + * @brief Present the contents of the back buffer of the provided context to + * the screen. + * + * The opengl system must be initialized before this call. + * + * @param[in] glWindow Pointer to the opengl window. + * @param[in] context Pointer to the context. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from a thread that has a + * bound context. + * + * @since 2.0 + * @sa palMakeContextCurrent + */ +PAL_API PalResult PAL_CALL palSwapBuffers( + PalGLWindow* glWindow, + PalGLContext* context); + +/** + * @brief Set the swap interval for the current context. + * + * The opengl system must be initialized before this call. + * This affects the currently bound context on the calling thread. + * `PAL_GL_EXTENSION_SWAP_CONTROL` must be supported otherwise undefined behavoir. + * + * @param[in] interval The swap interval + * + * Thread safety: Must only be called from a thread with a bound + * context. + * + * @since 2.0 + * @sa palMakeContextCurrent + */ +PAL_API void PAL_CALL palSetSwapInterval(int32_t interval); + +/** + * @brief Get supported opengl APIs + * + * @param[in] instance The instance (eg. HINSTANCE or XDisplay). + * + * @return An array of bools or `nullptr` on failure. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API const PalBool* PAL_CALL palGetSupportedGLAPIs(void* instance); + +/** @} */ + +#endif // _PAL_OPENGL_H diff --git a/include/pal2/pal_system.h b/include/pal2/pal_system.h new file mode 100644 index 00000000..c60227dc --- /dev/null +++ b/include/pal2/pal_system.h @@ -0,0 +1,176 @@ +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_system System + * @ingroup pal_system + * @{ + */ + +#ifndef _PAL_SYSTEM_H +#define _PAL_SYSTEM_H + +#include "pal_core.h" + +#define PAL_PLATFORM_NAME_SIZE 32 +#define PAL_CPU_VENDOR_NAME_SIZE 16 +#define PAL_CPU_MODEL_NAME_SIZE 64 + +#define PAL_CPU_ARCH_UNKNOWN 0 +#define PAL_CPU_ARCH_X86 1 +#define PAL_CPU_ARCH_X86_64 2 +#define PAL_CPU_ARCH_ARM 3 +#define PAL_CPU_ARCH_ARM64 4 +#define PAL_CPU_ARCH_COUNT 5 + +#define PAL_CPU_FEATURE_SSE (1ULL << 0) +#define PAL_CPU_FEATURE_SSE2 (1ULL << 1) +#define PAL_CPU_FEATURE_SSE3 (1ULL << 2) +#define PAL_CPU_FEATURE_SSSE3 (1ULL << 3) +#define PAL_CPU_FEATURE_SSE41 (1ULL << 4) /**< SSE4.1.*/ +#define PAL_CPU_FEATURE_SSE42 (1ULL << 5) /**< SSE4.2.*/ +#define PAL_CPU_FEATURE_AVX (1ULL << 6) +#define PAL_CPU_FEATURE_AVX2 (1ULL << 7) +#define PAL_CPU_FEATURE_AVX512F (1ULL << 8) +#define PAL_CPU_FEATURE_FMA3 (1ULL << 9) +#define PAL_CPU_FEATURE_BMI1 (1ULL << 10) +#define PAL_CPU_FEATURE_BMI2 (1ULL << 11) + +#define PAL_PLATFORM_TYPE_WINDOWS 0 +#define PAL_PLATFORM_TYPE_LINUX 1 +#define PAL_PLATFORM_TYPE_MACOS 2 +#define PAL_PLATFORM_TYPE_ANDROID 3 +#define PAL_PLATFORM_TYPE_IOS 4 +#define PAL_PLATFORM_TYPE_COUNT 5 + +#define PAL_PLATFORM_API_TYPE_WIN32 0 +#define PAL_PLATFORM_API_TYPE_WAYLAND 1 +#define PAL_PLATFORM_API_TYPE_X11 2 +#define PAL_PLATFORM_API_TYPE_COCOA 3 +#define PAL_PLATFORM_API_TYPE_ANDRIOD 4 +#define PAL_PLATFORM_API_TYPE_UIKIT 5 +#define PAL_PLATFORM_API_TYPE_HEADLESS 6 +#define PAL_PLATFORM_API_TYPE_COUNT 7 + +/** + * @typedef PalCpuArch + * @brief CPU achitecture. This is not a bitmask. + * + * All CPU achitectures follow the format `PAL_CPU_ARCH_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCpuArch; + +/** + * @typedef PalCpuFeatures + * @brief CPU features (instruction sets). + * + * All CPU features sets follow the format `PAL_CPU_FEATURE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint64_t PalCpuFeatures; + +/** + * @typedef PalPlatformType + * @brief Platform types. This is not a bitmask. + * + * This is the family name (eg. This does not show if its Windows 7 or Windows 8 + * etc). + * + * All platform types follow the format `PAL_PLATFORM_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPlatformType; + +/** + * @typedef PalPlatformApiType + * @brief Platform API types. This is not a bitmask. + * + * This is the API the playform uses. Most platforms support only one (eg. + * Windows). + * + * All platform API types follow the format `PAL_PLATFORM_API_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalPlatformApiType; + +/** + * @struct PalPlatformInfo + * @brief Information about a platform (OS). + * + * @since 2.0 + */ +typedef struct { + PalPlatformType type; /**< (eg. `PAL_PLATFORM_TYPE_WINDOWS`).*/ + PalPlatformApiType apiType; /**< (eg. `PAL_PLATFORM_API_TYPE_WIN32`).*/ + uint32_t totalMemory; /**< Total Disk space (memory) in GB.*/ + uint32_t totalRAM; /**< Total CPU RAM (memory) in MB.*/ + PalVersion version; /**< Platform version.*/ + char name[PAL_PLATFORM_NAME_SIZE]; /**< (eg. Windows 11.22000).*/ +} PalPlatformInfo; + +/** + * @struct PalCPUInfo + * @brief Information about a CPU. + * + * @since 2.0 + */ +typedef struct { + PalCpuFeatures features; /**< Supported CPU features (instructions).*/ + PalCpuArch architecture; /**< (eg. `PAL_CPU_ARCH_X86_64`).*/ + uint32_t numCores; /**< Number of cores.*/ + uint32_t cacheL1; /**< L1 cache in KB.*/ + uint32_t cacheL2; /**< L2 cache in KB.*/ + uint32_t cacheL3; /**< L3 cache in KB.*/ + uint32_t numLogicalProcessors; /**< Number of CPUs.*/ + char vendor[PAL_CPU_VENDOR_NAME_SIZE]; /**< CPU vendor name.*/ + char model[PAL_CPU_MODEL_NAME_SIZE]; /**< CPU modal name.*/ +} PalCPUInfo; + +/** + * @brief Get platform (OS) information. + * + * @param[out] info Pointer to a PalPlatformInfo to receive the platform info. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread-safe if `info` is per thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetPlatformInfo(PalPlatformInfo* info); + +/** + * @brief Get CPU information. + * + * @param[in] allocator Optional user provided allocator. Set to `nullptr` to + * use default. + * @param[out] info Pointer to a PalCPUInfo to receive the CPU info. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Thread-safe if the proivded allocator is + * thread safe and `info` is per thread. The default allocator is thread safe. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetCPUInfo( + const PalAllocator* allocator, + PalCPUInfo* info); + +/** @} */ + +#endif // _PAL_SYSTEM_H diff --git a/include/pal/pal_thread.h b/include/pal2/pal_thread.h similarity index 63% rename from include/pal/pal_thread.h rename to include/pal2/pal_thread.h index 666f27ed..d67dbee3 100644 --- a/include/pal/pal_thread.h +++ b/include/pal2/pal_thread.h @@ -1,31 +1,13 @@ /** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. */ /** * @defgroup pal_thread Thread - * Thread PAL functionality such as Threads, mutxes, condition variables and - * TLS. - * + * @ingroup pal_thread * @{ */ @@ -34,30 +16,37 @@ freely, subject to the following restrictions: #include "pal_core.h" -/** - * @typedef PalTLSId - * @brief Opaque handle to a Thread Local Storage. - * - * @since 1.0 - * @ingroup pal_thread - */ -typedef Uint32 PalTLSId; +#define PAL_THREAD_FEATURE_STACK_SIZE (1U << 0) +#define PAL_THREAD_FEATURE_PRIORITY (1U << 1) +#define PAL_THREAD_FEATURE_AFFINITY (1U << 2) +#define PAL_THREAD_FEATURE_NAME (1U << 3) + +#define PAL_THREAD_PRIORITY_LOW 0 +#define PAL_THREAD_PRIORITY_NORMAL 1 +#define PAL_THREAD_PRIORITY_HIGH 2 +#define PAL_THREAD_PRIORITY_COUNT 3 /** * @struct PalThread * @brief Opaque handle to a thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ typedef struct PalThread PalThread; +/** + * @typedef PalTLSId + * @brief Opaque handle to a Thread Local Storage. + * + * @since 2.0 + */ +typedef uint32_t PalTLSId; + /** * @struct PalMutex * @brief Opaque handle to a mutex. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ typedef struct PalMutex PalMutex; @@ -65,69 +54,55 @@ typedef struct PalMutex PalMutex; * @struct PalCondVar * @brief Opaque handle to a condition variable. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ typedef struct PalCondVar PalCondVar; /** - * @typedef PalThreadFn - * @brief Function pointer type used for thread entry function. - * - * @param[in] arg Optional pointer to user data. Can be nullptr. + * @typedef PalThreadFeatures + * @brief Thread system features. * - * @return The return value of the thread as a pointer. + * All thread features follow the format `PAL_THREAD_FEATURE_**` for + * consistency and API use. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ -typedef void* (*PalThreadFn)(void* arg); +typedef uint32_t PalThreadFeatures; /** - * @typedef PaTlsDestructorFn - * @brief Function pointer type used for TLS. - * - * This is called when the TLS is destroyed and its value is not nullptr. + * @typedef PalThreadPriority + * @brief Thread priority types. This is not a bitmask. * - * @param[in] userData Optional pointer to user data. Can be nullptr. + * All thread priority types follow the format `PAL_THREAD_PRIORITY_**` for + * consistency and API use. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ -typedef void (*PaTlsDestructorFn)(void* userData); +typedef uint32_t PalThreadPriority; /** - * @enum PalThreadFeatures - * @brief Thread system features. + * @typedef PalThreadFn + * @brief Function pointer type used for thread entry function. * - * All thread features follow the format `PAL_THREAD_FEATURE_**` for - * consistency and API use. + * @param[in] arg Optional pointer to user data. Can be `nullptr`. * - * @since 1.0 - * @ingroup pal_thread + * @return The return value of the thread as a pointer. + * + * @since 2.0 */ -typedef enum { - PAL_THREAD_FEATURE_STACK_SIZE = PAL_BIT(0), - PAL_THREAD_FEATURE_PRIORITY = PAL_BIT(1), - PAL_THREAD_FEATURE_AFFINITY = PAL_BIT(2), - PAL_THREAD_FEATURE_NAME = PAL_BIT(3) -} PalThreadFeatures; +typedef void* (*PalThreadFn)(void* arg); /** - * @enum PalThreadPriority - * @brief Thread priority types. This is not a bitmask enum. + * @typedef PaTlsDestructorFn + * @brief Function pointer type used for TLS. * - * All thread priority types follow the format `PAL_THREAD_PRIORITY_**` for - * consistency and API use. + * This is called when the TLS is destroyed and its value is not `nullptr`. * - * @since 1.0 - * @ingroup pal_thread + * @param[in] userData Optional pointer to user data. Can be `nullptr`. + * + * @since 2.0 */ -typedef enum { - PAL_THREAD_PRIORITY_LOW, - PAL_THREAD_PRIORITY_NORMAL, - PAL_THREAD_PRIORITY_HIGH -} PalThreadPriority; +typedef void (*PaTlsDestructorFn)(void* userData); /** * @struct PalThreadCreateInfo @@ -135,14 +110,13 @@ typedef enum { * * Uninitialized fields may result in undefined behavior. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ typedef struct { - Uint64 stackSize; /**< Set to 0 to use default*/ - const PalAllocator* allocator; /**< Set to nullptr to use default.*/ - PalThreadFn entry; /**< Thread entry function*/ - void* arg; /**< Optional user-provided data. Can be nullptr.*/ + uint64_t stackSize; /**< Set to 0 to use default*/ + const PalAllocator* allocator; /**< Set to `nullptr` to use default.*/ + PalThreadFn entry; /**< Thread entry function. Must not be `nullptr`*/ + void* arg; /**< Optional user-provided data. Can be `nullptr`.*/ } PalThreadCreateInfo; /** @@ -156,20 +130,17 @@ typedef struct { * The created thread starts executing from the entry function. The thread runs * until the entry function has finished executing or its detached. * - * @param[in] info Pointer to a PalThreadCreateInfo struct that specifies - * paramters. Must not be nullptr. - * @param[out] outThread Pointer to a PalThread to recieve the created - * thread. Must not be nullptr. + * @param[in] info Pointer to a PalThreadCreateInfo struct that specifies parameters. + * @param[out] outThread Pointer to a PalThread to recieve the created thread. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe if the provided allocator is - * thread safe and `outThread` is thread local. The default allocator is + * Thread safety: Thread safe if the provided allocator is + * thread safe and `outThread` is per thread. The default allocator is * thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palDetachThread */ PAL_API PalResult PAL_CALL palCreateThread( @@ -184,36 +155,32 @@ PAL_API PalResult PAL_CALL palCreateThread( * * @param[in] thread Pointer to the thread. * @param[out] retval Optionally pointer to get the threads exit value. - * Pass the address of the pointer. Internally it will be reinterpreted into a - * pointer-to-pointer. This is for ABI stability. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe if `retval` is thread local. + * Thread safety: Thread safe if `retval` is per thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalResult PAL_CALL palJoinThread( PalThread* thread, - void* retval); + void** retval); /** * @brief Release the thread's resources and destroy it. * - * This function must be called when the thread is done executing. + * Must be called when the thread is done executing. * After this call, the thread cannot be attached or used anymore. - * If the thread is invalid or nullptr, this function returns silently. + * If the thread is invalid or `nullptr`, this function returns silently. * * This must not be called on a thread that has been attached. * * @param[in] thread Pointer to the thread to detach. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API void PAL_CALL palDetachThread(PalThread* thread); @@ -222,33 +189,30 @@ PAL_API void PAL_CALL palDetachThread(PalThread* thread); * * @param[in] milliseconds Number of milliseconds to sleep. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ -PAL_API void PAL_CALL palSleep(Uint64 milliseconds); +PAL_API void PAL_CALL palSleep(uint64_t milliseconds); /** * @brief Yield the remainder of the calling threads time sliced, * allowing other threads of equal priority to run. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API void PAL_CALL palYield(); /** * @brief Get the current executing thread. * - * @return The current thread on success or nullptr on failure. + * @return The current thread on success or `nullptr` on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalThread* PAL_CALL palGetCurrentThread(); @@ -259,63 +223,58 @@ PAL_API PalThread* PAL_CALL palGetCurrentThread(); * * @return The thread features on success or 0 on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalThreadFeatures PAL_CALL palGetThreadFeatures(); /** * @brief Get the priority of the provided thread. * - * `PAL_THREAD_FEATURE_PRIORITY` must be supported. + * `PAL_THREAD_FEATURE_PRIORITY` must be supported otherwise undefined behavior. * * @param[in] thread The thread to query priority for. * * @return The thread priority on success or 0 on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread); /** * @brief Get the affinity of the provided thread. * - * `PAL_THREAD_FEATURE_AFFINITY` must be supported. Thread affinity is the - * number of CPU cores the thread is allowed to be executed on. + * `PAL_THREAD_FEATURE_AFFINITY` must be supported otherwise undefined behavior. * * @param[in] thread The thread to query affinity for. * * @return The thread affinity on success or 0 on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ -PAL_API Uint64 PAL_CALL palGetThreadAffinity(PalThread* thread); +PAL_API uint64_t PAL_CALL palGetThreadAffinity(PalThread* thread); /** * @brief Get the name of the provided thread. * - * `PAL_THREAD_FEATURE_NAME` must be supported. - * fails. Set the buffer to nullptr to get the size of the thread name in bytes. + * `PAL_THREAD_FEATURE_NAME` must be supported otherwise undefined behavior. + * Set the buffer to `nullptr` to get the size of the thread name in bytes. * * If the size of the provided buffer is less than the actual size of thread * name, PAL will write upto that limit. * - * * @param[in] thread The thread to query its name. * @param[in] bufferSize Size of the provided buffer in bytes. * @param[out] outSize The actual size of the thread name in bytes. * @param[out] outBuffer Pointer to a user provided buffer to recieve the name. - * Can be nullptr. + * Can be `nullptr`. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * * @note On Linux: Thread names are limited to 16 characters including the null * terminator. @@ -323,20 +282,19 @@ PAL_API Uint64 PAL_CALL palGetThreadAffinity(PalThread* thread); * @note On MacOS: Thread names are limited to 64 characters including the null * terminator. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palSetThreadName */ -PAL_API PalResult PAL_CALL palGetThreadName( +PAL_API void PAL_CALL palGetThreadName( PalThread* thread, - Uint64 bufferSize, - Uint64* outSize, + uint64_t bufferSize, + uint64_t* outSize, char* outBuffer); /** * @brief Set the priority of the provided thread. * - * `PAL_THREAD_FEATURE_PRIORITY` must be supported. + * `PAL_THREAD_FEATURE_PRIORITY` must be supported otherwise undefined behavior. * * @param[in] thread The thread to set priority for. * @param[in] priority The new thread priority. @@ -346,10 +304,9 @@ PAL_API PalResult PAL_CALL palGetThreadName( * * @return * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalResult PAL_CALL palSetThreadPriority( PalThread* thread, @@ -358,15 +315,13 @@ PAL_API PalResult PAL_CALL palSetThreadPriority( /** * @brief Set the affinity of the provided thread. * - * `PAL_THREAD_FEATURE_AFFINITY` must be supported. - * Thread affinity is the number of CPU cores the thread is allowed to be - * executed on. + * `PAL_THREAD_FEATURE_AFFINITY` must be supported otherwise undefined behavior. * * To be safe, get the number of CPU cores and use that to build the CPU mask. * Example: we set a thread to the first and second CPU core. * * @code - * Uint64 cpuMask = PAL_BIT(0) | PAL_BIT(1). + * uint64_t cpuMask = (1ULL << 0) | (1ULL << 1). * @endcode * * @param[in] thread The thread to set affinity for. @@ -375,19 +330,18 @@ PAL_API PalResult PAL_CALL palSetThreadPriority( * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 */ PAL_API PalResult PAL_CALL palSetThreadAffinity( PalThread* thread, - Uint64 mask); + uint64_t mask); /** * @brief Set the name of the provided thread. * - * `PAL_THREAD_FEATURE_NAME` must be supported. + * `PAL_THREAD_FEATURE_NAME` must be supported otherwise undefined behavior. * The thread name will be visible in debuggers and the Task Manager (Windows). * * @param[in] thread The thread @@ -396,7 +350,7 @@ PAL_API PalResult PAL_CALL palSetThreadAffinity( * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * * @note On Linux: Thread names are limited to 16 characters including the null * terminator. @@ -404,8 +358,7 @@ PAL_API PalResult PAL_CALL palSetThreadAffinity( * @note On MacOS: Thread names are limited to 64 characters including the null * terminator. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palGetThreadName */ PAL_API PalResult PAL_CALL palSetThreadName( @@ -420,14 +373,13 @@ PAL_API PalResult PAL_CALL palSetThreadName( * the TLS has a valid value. * * @param[in] destructor Pointer to the TLS destructor function. Can be - * nullptr. + * `nullptr`. * * @return The TLS on success or 0 on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palDestroyTLS */ PAL_API PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor); @@ -437,10 +389,9 @@ PAL_API PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor); * * @param[in] id The TLS to destroy. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palCreateTL */ PAL_API void PAL_CALL palDestroyTLS(PalTLSId id); @@ -450,12 +401,11 @@ PAL_API void PAL_CALL palDestroyTLS(PalTLSId id); * * @param[in] id The TLS to query value. * - * @return the value on success or nullptr on failure. + * @return the value on success or `nullptr` on failure. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palSetTLS */ PAL_API void* PAL_CALL palGetTLS(PalTLSId id); @@ -466,10 +416,9 @@ PAL_API void* PAL_CALL palGetTLS(PalTLSId id); * @param[in] id The TLS to set value to. * @param[in] data The value to set for the calling thread * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palGetTLS */ PAL_API void PAL_CALL palSetTLS( @@ -479,19 +428,16 @@ PAL_API void PAL_CALL palSetTLS( /** * @brief Create a mutex. * - * @param[in] allocator Optional user-provided allocator. Set to nullptr to use - * default. + * @param[in] allocator Optional user-provided allocator. Set to `nullptr` to use default. * @param[out] outMutex Pointer to a PalMutex to recieve the created mutex. - * Must not be nullptr. * * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe if the provided allocator is - * thread safe and `outMutex` is thread local. + * Thread safety: Thread safe if the provided allocator is + * thread safe and `outMutex` is per thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palDestroyMutex */ PAL_API PalResult PAL_CALL palCreateMutex( @@ -506,11 +452,10 @@ PAL_API PalResult PAL_CALL palCreateMutex( * * @param[in] mutex Pointer to the mutex. * - * Thread safety: This function is thread safe if the allocator used to create - * the mutex is thread safe and `mutex` is thread local. + * Thread safety: Thread safe if the allocator used to create + * the mutex is thread safe and `mutex` is per thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palCreateMutex */ PAL_API void PAL_CALL palDestroyMutex(PalMutex* mutex); @@ -520,10 +465,9 @@ PAL_API void PAL_CALL palDestroyMutex(PalMutex* mutex); * * @param[in] mutex Pointer to the mutex to lock. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palUnlockMutex */ PAL_API void PAL_CALL palLockMutex(PalMutex* mutex); @@ -535,10 +479,9 @@ PAL_API void PAL_CALL palLockMutex(PalMutex* mutex); * * @param[in] mutex Pointer to the mutex to unlock. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palLockMutex */ PAL_API void PAL_CALL palUnlockMutex(PalMutex* mutex); @@ -546,7 +489,7 @@ PAL_API void PAL_CALL palUnlockMutex(PalMutex* mutex); /** * @brief Create a condition variable. * - * @param[in] allocator Optional user-provided allocator. Set to nullptr to use + * @param[in] allocator Optional user-provided allocator. Set to `nullptr` to use * default. * @param[out] outCondVar Pointer to a PalCondVar to recieve the created * condition variable. @@ -554,11 +497,10 @@ PAL_API void PAL_CALL palUnlockMutex(PalMutex* mutex); * @return `PAL_RESULT_SUCCESS` on success or a result code on * failure. Call palFormatResult() for more information. * - * Thread safety: This function is thread safe if the provided allocator is - * thread safe and `outCondVar` is thread local. + * Thread safety: Thread safe if the provided allocator is + * thread safe and `outCondVar` is per thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palDestroyCondVar */ PAL_API PalResult PAL_CALL palCreateCondVar( @@ -574,11 +516,10 @@ PAL_API PalResult PAL_CALL palCreateCondVar( * * @param[in] condVar Pointer to the condition to destroy * - * Thread safety: This function is thread safe if the allocator used to create - * the condition varibale is thread safe and `condVar` is thread local. + * Thread safety: Thread safe if the allocator used to create + * the condition varibale is thread safe and `condVar` is per thread. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palCreateCondVar */ PAL_API void PAL_CALL palDestroyCondVar(PalCondVar* condVar); @@ -598,10 +539,9 @@ PAL_API void PAL_CALL palDestroyCondVar(PalCondVar* condVar); * @param[in] condVar Pointer to the condition variable. * @param[in] mutex Pointer to the mutex. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palWaitCondVarTimeout */ PAL_API PalResult PAL_CALL palWaitCondVar( @@ -619,26 +559,24 @@ PAL_API PalResult PAL_CALL palWaitCondVar( * @param[in] mutex Pointer to the mutex. * @param[in] milliseconds Timeout in milliseconds. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palWaitCondVar */ PAL_API PalResult PAL_CALL palWaitCondVarTimeout( PalCondVar* condVar, PalMutex* mutex, - Uint64 milliseconds); + uint64_t milliseconds); /** * @brief Wake a single thread waiting on the condition variable. * * @param[in] condVar Pointer to the condition variable. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0 - * @ingroup pal_thread + * @since 2.0 * @sa palBroadcastCondVar */ PAL_API void PAL_CALL palSignalCondVar(PalCondVar* condVar); @@ -648,14 +586,13 @@ PAL_API void PAL_CALL palSignalCondVar(PalCondVar* condVar); * * @param[in] condVar Pointer to the condition variable. * - * Thread safety: This function is thread safe. + * Thread safety: Thread safe. * - * @since 1.0. - * @ingroup pal_thread + * @since 2.0 * @sa palSignalCondVar */ PAL_API void PAL_CALL palBroadcastCondVar(PalCondVar* condVar); -/** @} */ // end of pal_thread group +/** @} */ -#endif // _PAL_THREAD_H \ No newline at end of file +#endif // _PAL_THREAD_H diff --git a/include/pal2/pal_video.h b/include/pal2/pal_video.h new file mode 100644 index 00000000..b6269b50 --- /dev/null +++ b/include/pal2/pal_video.h @@ -0,0 +1,1671 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +/** + * @defgroup pal_video Video + * @ingroup pal_video + * @{ + */ + +#ifndef _PAL_VIDEO_H +#define _PAL_VIDEO_H + +#include "pal_event.h" + +#define PAL_MONITOR_NAME_SIZE 32 + +#define PAL_VIDEO_FEATURE_HIGH_DPI (1ULL << 0) +#define PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION (1ULL << 1) +#define PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION (1ULL << 2) +#define PAL_VIDEO_FEATURE_BORDERLESS_WINDOW (1ULL << 3) +#define PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW (1ULL << 4) +#define PAL_VIDEO_FEATURE_TOOL_WINDOW (1ULL << 5) +#define PAL_VIDEO_FEATURE_MONITOR_SET_MODE (1ULL << 6) +#define PAL_VIDEO_FEATURE_MONITOR_GET_MODE (1ULL << 7) +#define PAL_VIDEO_FEATURE_MULTI_MONITORS (1ULL << 8) +#define PAL_VIDEO_FEATURE_WINDOW_SET_SIZE (1ULL << 9) +#define PAL_VIDEO_FEATURE_WINDOW_GET_SIZE (1ULL << 10) +#define PAL_VIDEO_FEATURE_WINDOW_SET_POS (1ULL << 11) +#define PAL_VIDEO_FEATURE_WINDOW_GET_POS (1ULL << 12) +#define PAL_VIDEO_FEATURE_WINDOW_SET_STATE (1ULL << 13) +#define PAL_VIDEO_FEATURE_WINDOW_GET_STATE (1ULL << 14) +#define PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY (1ULL << 15) +#define PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY (1ULL << 16) +#define PAL_VIDEO_FEATURE_WINDOW_SET_TITLE (1ULL << 17) +#define PAL_VIDEO_FEATURE_WINDOW_GET_TITLE (1ULL << 18) +#define PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX (1ULL << 19) +#define PAL_VIDEO_FEATURE_NO_MINIMIZEBOX (1ULL << 20) +#define PAL_VIDEO_FEATURE_CLIP_CURSOR (1ULL << 21) +#define PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION (1ULL << 22) +#define PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY (1ULL << 23) +#define PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL (1ULL << 24) +#define PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS (1ULL << 25) +#define PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS (1ULL << 26) +#define PAL_VIDEO_FEATURE_WINDOW_SET_STYLE (1ULL << 27) +#define PAL_VIDEO_FEATURE_WINDOW_GET_STYLE (1ULL << 28) +#define PAL_VIDEO_FEATURE_CURSOR_SET_POS (1ULL << 29) +#define PAL_VIDEO_FEATURE_CURSOR_GET_POS (1ULL << 30) +#define PAL_VIDEO_FEATURE_WINDOW_SET_ICON (1ULL << 31) +#define PAL_VIDEO_FEATURE_TOPMOST_WINDOW (1ULL << 32) +#define PAL_VIDEO_FEATURE_DECORATED_WINDOW (1ULL << 33) +#define PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY (1ULL << 34) +#define PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR (1ULL << 35) +#define PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY (1ULL << 36) +#define PAL_VIDEO_FEATURE_FOREIGN_WINDOWS (1ULL << 37) +#define PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE (1ULL << 38) +#define PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR (1ULL << 39) + +#define PAL_ORIENTATION_LANDSCAPE 0 +#define PAL_ORIENTATION_PORTRAIT 1 +#define PAL_ORIENTATION_LANDSCAPE_FLIPPED 2 +#define PAL_ORIENTATION_PORTRAIT_FLIPPED 3 +#define PAL_ORIENTATION_COUNT 4 + +#define PAL_WINDOW_STYLE_RESIZABLE (1U << 0) +#define PAL_WINDOW_STYLE_TRANSPARENT (1U << 1) +#define PAL_WINDOW_STYLE_TOPMOST (1U << 2) +#define PAL_WINDOW_STYLE_NO_MINIMIZEBOX (1U << 3) +#define PAL_WINDOW_STYLE_NO_MAXIMIZEBOX (1U << 4) +#define PAL_WINDOW_STYLE_TOOL (1U << 5) +#define PAL_WINDOW_STYLE_BORDERLESS (1U << 6) + +#define PAL_WINDOW_STATE_NORMAL 0 +#define PAL_WINDOW_STATE_MAXIMIZED 1 +#define PAL_WINDOW_STATE_MINIMIZED 2 +#define PAL_WINDOW_STATE_RESTORED 3 +#define PAL_WINDOW_STATE_COUNT 4 + +#define PAL_FLASH_FLAG_STOP 0 /**< Stop flashing.*/ +#define PAL_FLASH_FLAG_CAPTION (1U << 0) /**< Flash the titlebar of the window.*/ +#define PAL_FLASH_FLAG_TRAY (1U << 1) /**< Flash the icon of the window.*/ + +#define PAL_FBCONFIG_BACKEND_PAL_OPENGL 0 /**< Use PAL opengl module backend.*/ +#define PAL_FBCONFIG_BACKEND_EGL 1 +#define PAL_FBCONFIG_BACKEND_GLX 2 +#define PAL_FBCONFIG_BACKEND_WGL 3 +#define PAL_FBCONFIG_BACKEND_COUNT 4 + +#define PAL_SCANCODE_UNKNOWN 0 +#define PAL_SCANCODE_A 1 +#define PAL_SCANCODE_B 2 +#define PAL_SCANCODE_C 3 +#define PAL_SCANCODE_D 4 +#define PAL_SCANCODE_E 5 +#define PAL_SCANCODE_F 6 +#define PAL_SCANCODE_G 7 +#define PAL_SCANCODE_H 8 +#define PAL_SCANCODE_I 9 +#define PAL_SCANCODE_J 10 +#define PAL_SCANCODE_K 11 +#define PAL_SCANCODE_L 12 +#define PAL_SCANCODE_M 13 +#define PAL_SCANCODE_N 14 +#define PAL_SCANCODE_O 15 +#define PAL_SCANCODE_P 16 +#define PAL_SCANCODE_Q 17 +#define PAL_SCANCODE_R 18 +#define PAL_SCANCODE_S 19 +#define PAL_SCANCODE_T 20 +#define PAL_SCANCODE_U 21 +#define PAL_SCANCODE_V 22 +#define PAL_SCANCODE_W 23 +#define PAL_SCANCODE_X 24 +#define PAL_SCANCODE_Y 25 +#define PAL_SCANCODE_Z 26 +#define PAL_SCANCODE_0 27 +#define PAL_SCANCODE_1 28 +#define PAL_SCANCODE_2 29 +#define PAL_SCANCODE_3 30 +#define PAL_SCANCODE_4 31 +#define PAL_SCANCODE_5 32 +#define PAL_SCANCODE_6 33 +#define PAL_SCANCODE_7 34 +#define PAL_SCANCODE_8 35 +#define PAL_SCANCODE_9 36 +#define PAL_SCANCODE_F1 37 +#define PAL_SCANCODE_F2 38 +#define PAL_SCANCODE_F3 39 +#define PAL_SCANCODE_F4 40 +#define PAL_SCANCODE_F5 41 +#define PAL_SCANCODE_F6 42 +#define PAL_SCANCODE_F7 43 +#define PAL_SCANCODE_F8 44 +#define PAL_SCANCODE_F9 45 +#define PAL_SCANCODE_F10 46 +#define PAL_SCANCODE_F11 47 +#define PAL_SCANCODE_F12 48 +#define PAL_SCANCODE_ESCAPE 49 +#define PAL_SCANCODE_ENTER 50 +#define PAL_SCANCODE_TAB 51 +#define PAL_SCANCODE_BACKSPACE 52 +#define PAL_SCANCODE_SPACE 53 +#define PAL_SCANCODE_CAPSLOCK 54 +#define PAL_SCANCODE_NUMLOCK 55 +#define PAL_SCANCODE_SCROLLLOCK 56 +#define PAL_SCANCODE_LSHIFT 57 +#define PAL_SCANCODE_RSHIFT 58 +#define PAL_SCANCODE_LCTRL 59 +#define PAL_SCANCODE_RCTRL 60 +#define PAL_SCANCODE_LALT 61 +#define PAL_SCANCODE_RALT 62 +#define PAL_SCANCODE_LEFT 63 +#define PAL_SCANCODE_RIGHT 64 +#define PAL_SCANCODE_UP 65 +#define PAL_SCANCODE_DOWN 66 +#define PAL_SCANCODE_INSERT 67 +#define PAL_SCANCODE_DELETE 68 +#define PAL_SCANCODE_HOME 69 +#define PAL_SCANCODE_END 70 +#define PAL_SCANCODE_PAGEUP 71 +#define PAL_SCANCODE_PAGEDOWN 72 +#define PAL_SCANCODE_KP_0 73 +#define PAL_SCANCODE_KP_1 74 +#define PAL_SCANCODE_KP_2 75 +#define PAL_SCANCODE_KP_3 76 +#define PAL_SCANCODE_KP_4 77 +#define PAL_SCANCODE_KP_5 78 +#define PAL_SCANCODE_KP_6 79 +#define PAL_SCANCODE_KP_7 80 +#define PAL_SCANCODE_KP_8 81 +#define PAL_SCANCODE_KP_9 82 +#define PAL_SCANCODE_KP_ENTER 83 +#define PAL_SCANCODE_KP_ADD 84 +#define PAL_SCANCODE_KP_SUBTRACT 85 +#define PAL_SCANCODE_KP_MULTIPLY 86 +#define PAL_SCANCODE_KP_DIVIDE 87 +#define PAL_SCANCODE_KP_DECIMAL 88 +#define PAL_SCANCODE_KP_EQUAL 89 +#define PAL_SCANCODE_PRINTSCREEN 90 +#define PAL_SCANCODE_PAUSE 91 +#define PAL_SCANCODE_MENU 92 +#define PAL_SCANCODE_APOSTROPHE 93 +#define PAL_SCANCODE_BACKSLASH 94 +#define PAL_SCANCODE_COMMA 95 +#define PAL_SCANCODE_EQUAL 96 +#define PAL_SCANCODE_GRAVEACCENT 97 +#define PAL_SCANCODE_SUBTRACT 98 +#define PAL_SCANCODE_PERIOD 99 +#define PAL_SCANCODE_SEMICOLON 100 +#define PAL_SCANCODE_SLASH 101 +#define PAL_SCANCODE_LBRACKET 102 +#define PAL_SCANCODE_RBRACKET 103 +#define PAL_SCANCODE_LSUPER 104 +#define PAL_SCANCODE_RSUPER 105 +#define PAL_SCANCODE_COUNT 106 + +#define PAL_KEYCODE_UNKNOWN 0 +#define PAL_KEYCODE_A 1 +#define PAL_KEYCODE_B 2 +#define PAL_KEYCODE_C 3 +#define PAL_KEYCODE_D 4 +#define PAL_KEYCODE_E 5 +#define PAL_KEYCODE_F 6 +#define PAL_KEYCODE_G 7 +#define PAL_KEYCODE_H 8 +#define PAL_KEYCODE_I 9 +#define PAL_KEYCODE_J 10 +#define PAL_KEYCODE_K 11 +#define PAL_KEYCODE_L 12 +#define PAL_KEYCODE_M 13 +#define PAL_KEYCODE_N 14 +#define PAL_KEYCODE_O 15 +#define PAL_KEYCODE_P 16 +#define PAL_KEYCODE_Q 17 +#define PAL_KEYCODE_R 18 +#define PAL_KEYCODE_S 19 +#define PAL_KEYCODE_T 20 +#define PAL_KEYCODE_U 21 +#define PAL_KEYCODE_V 22 +#define PAL_KEYCODE_W 23 +#define PAL_KEYCODE_X 24 +#define PAL_KEYCODE_Y 25 +#define PAL_KEYCODE_Z 26 +#define PAL_KEYCODE_0 27 +#define PAL_KEYCODE_1 28 +#define PAL_KEYCODE_2 29 +#define PAL_KEYCODE_3 30 +#define PAL_KEYCODE_4 31 +#define PAL_KEYCODE_5 32 +#define PAL_KEYCODE_6 33 +#define PAL_KEYCODE_7 34 +#define PAL_KEYCODE_8 35 +#define PAL_KEYCODE_9 36 +#define PAL_KEYCODE_F1 37 +#define PAL_KEYCODE_F2 38 +#define PAL_KEYCODE_F3 39 +#define PAL_KEYCODE_F4 40 +#define PAL_KEYCODE_F5 41 +#define PAL_KEYCODE_F6 42 +#define PAL_KEYCODE_F7 43 +#define PAL_KEYCODE_F8 44 +#define PAL_KEYCODE_F9 45 +#define PAL_KEYCODE_F10 46 +#define PAL_KEYCODE_F11 47 +#define PAL_KEYCODE_F12 48 +#define PAL_KEYCODE_ESCAPE 49 +#define PAL_KEYCODE_ENTER 50 +#define PAL_KEYCODE_TAB 51 +#define PAL_KEYCODE_BACKSPACE 52 +#define PAL_KEYCODE_SPACE 53 +#define PAL_KEYCODE_CAPSLOCK 54 +#define PAL_KEYCODE_NUMLOCK 55 +#define PAL_KEYCODE_SCROLLLOCK 56 +#define PAL_KEYCODE_LSHIFT 57 +#define PAL_KEYCODE_RSHIFT 58 +#define PAL_KEYCODE_LCTRL 59 +#define PAL_KEYCODE_RCTRL 60 +#define PAL_KEYCODE_LALT 61 +#define PAL_KEYCODE_RALT 62 +#define PAL_KEYCODE_LEFT 63 +#define PAL_KEYCODE_RIGHT 64 +#define PAL_KEYCODE_UP 65 +#define PAL_KEYCODE_DOWN 66 +#define PAL_KEYCODE_INSERT 67 +#define PAL_KEYCODE_DELETE 68 +#define PAL_KEYCODE_HOME 69 +#define PAL_KEYCODE_END 70 +#define PAL_KEYCODE_PAGEUP 71 +#define PAL_KEYCODE_PAGEDOWN 72 +#define PAL_KEYCODE_KP_0 73 +#define PAL_KEYCODE_KP_1 74 +#define PAL_KEYCODE_KP_2 75 +#define PAL_KEYCODE_KP_3 76 +#define PAL_KEYCODE_KP_4 77 +#define PAL_KEYCODE_KP_5 78 +#define PAL_KEYCODE_KP_6 79 +#define PAL_KEYCODE_KP_7 80 +#define PAL_KEYCODE_KP_8 81 +#define PAL_KEYCODE_KP_9 82 +#define PAL_KEYCODE_KP_ENTER 83 +#define PAL_KEYCODE_KP_ADD 84 +#define PAL_KEYCODE_KP_SUBTRACT 85 +#define PAL_KEYCODE_KP_MULTIPLY 86 +#define PAL_KEYCODE_KP_DIVIDE 87 +#define PAL_KEYCODE_KP_DECIMAL 88 +#define PAL_KEYCODE_KP_EQUAL 89 +#define PAL_KEYCODE_PRINTSCREEN 90 +#define PAL_KEYCODE_PAUSE 91 +#define PAL_KEYCODE_MENU 92 +#define PAL_KEYCODE_APOSTROPHE 93 +#define PAL_KEYCODE_BACKSLASH 94 +#define PAL_KEYCODE_COMMA 95 +#define PAL_KEYCODE_EQUAL 96 +#define PAL_KEYCODE_GRAVEACCENT 97 +#define PAL_KEYCODE_SUBTRACT 98 +#define PAL_KEYCODE_PERIOD 99 +#define PAL_KEYCODE_SEMICOLON 100 +#define PAL_KEYCODE_SLASH 101 +#define PAL_KEYCODE_LBRACKET 102 +#define PAL_KEYCODE_RBRACKET 103 +#define PAL_KEYCODE_LSUPER 104 +#define PAL_KEYCODE_RSUPER 105 +#define PAL_KEYCODE_COUNT 106 + +#define PAL_MOUSE_BUTTON_UNKNOWN 0 +#define PAL_MOUSE_BUTTON_LEFT 1 +#define PAL_MOUSE_BUTTON_RIGHT 2 +#define PAL_MOUSE_BUTTON_MIDDLE 3 +#define PAL_MOUSE_BUTTON_X1 4 +#define PAL_MOUSE_BUTTON_X2 5 +#define PAL_MOUSE_BUTTON_COUNT 6 + +#define PAL_CURSOR_TYPE_ARROW 0 +#define PAL_CURSOR_TYPE_HAND 1 +#define PAL_CURSOR_TYPE_CROSS 2 +#define PAL_CURSOR_TYPE_IBEAM 3 +#define PAL_CURSOR_TYPE_WAIT 4 +#define PAL_CURSOR_TYPE_COUNT 5 + +/** + * @struct PalMonitor + * @brief Opaque handle to a monitor. + * + * @since 2.0 + */ +typedef struct PalMonitor PalMonitor; + +/** + * @struct PalWindow + * @brief Opaque handle to a window. + * + * @since 2.0 + */ +typedef struct PalWindow PalWindow; + +/** + * @struct PalIcon + * @brief Opaque handle to an icon. + * + * @since 2.0 + */ +typedef struct PalIcon PalIcon; + +/** + * @struct PalCursor + * @brief Opaque handle to a cursor. + * + * @since 2.0 + */ +typedef struct PalCursor PalCursor; + +/** + * @typedef PalVideoFeatures + * @brief Video system features. + * + * All video features follow the format `PAL_VIDEO_FEATURE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint64_t PalVideoFeatures; + +/** + * @typedef PalOrientation + * @brief Orientation types for a monitor. + * + * All orientation types follow the format `PAL_ORIENTATION_**` for consistency + * and API use. + * + * @since 2.0 + */ +typedef uint32_t PalOrientation; + +/** + * @typedef PalWindowStyle + * @brief Window styles. Multiple styles can be OR'ed together using bitwise + * OR operator (`|`). + * + * All window flags follow the format `PAL_WINDOW_STYLE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalWindowStyle; + +/** + * @typedef PalWindowState + * @brief Represents the current state of a window. + * + * All window states follow the format `PAL_WINDOW_STATE_**` for consistency and + * API use. + * + * @since 2.0 + */ +typedef uint32_t PalWindowState; + +/** + * @typedef PalFlashFlags + * @brief Flash flags. Multiple flash flags can be OR'ed together using bitwise + * OR operator (`|`). + * + * `PAL_FLASH_FLAG_STOP` is not a bit and must not be combined with other bits. + * + * All flash flags follow the format `PAL_FLASH_FLAG_**` for consistency and + * API use. + * + * @since 2.0 + */ +typedef uint32_t PalFlashFlags; + +/** + * @typedef PalFBConfigBackend + * @brief Represents the backend of a FBConfig. + * + * All FBConfig backends follow the format `PAL_FBCONFIG_BACKEND_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalFBConfigBackend; + +/** + * @typedef PalScancode + * @brief scancodes (layout independent keys) of a keyboard. + * + * All scancodes follow the format `PAL_SCANCODE_**` for consistency and + * API use. + * + * @since 2.0 + */ +typedef uint32_t PalScancode; + +/** + * @typedef PalKeycode + * @brief Keycodes (layout aware keys) of a keyboard. + * + * All keycodes follow the format `PAL_KEYCODE_**` for consistency and API + * use. + * + * @since 2.0 + */ +typedef uint32_t PalKeycode; + +/** + * @typedef PalMouseButton + * @brief Buttons of a mouse. + * + * All mouse buttons follow the format `PAL_MOUSE_BUTTON_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalMouseButton; + +/** + * @typedef PalCursorType + * @brief System cursor types. + * + * All cursor types follow the format `PAL_CURSOR_TYPE_**` for + * consistency and API use. + * + * @since 2.0 + */ +typedef uint32_t PalCursorType; + +/** + * @struct PalMonitorInfo + * @brief Information about a monitor. + * + * @since 2.0 + */ +typedef struct { + int32_t x; /**< X position in pixels.*/ + int32_t y; /**< Y position in pixels.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ + uint32_t dpi; /**< Display dots per inch.*/ + uint32_t refreshRate; /**< Refresh rate in Hz.*/ + PalOrientation orientation; /**< (eg. `PAL_ORIENTATION_LANDSCAPE`).*/ + PalBool primary; /**< `PAL_TRUE` if this is the primary monitor.*/ + char name[PAL_MONITOR_NAME_SIZE]; /**< Name of the monitor.*/ +} PalMonitorInfo; + +/** + * @struct PalMonitorMode + * @brief information about a monitor display mode. + * + * @since 2.0 + */ +typedef struct { + uint32_t bpp; /**< Bits per pixel.*/ + uint32_t refreshRate; /**< Refresh rate in Hz.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ +} PalMonitorMode; + +/** + * @struct PalFlashInfo + * @brief Parameters for flashing a window to request focus. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + PalFlashFlags flags; /**< (eg. `PAL_FLASH_FLAG_CAPTION`).*/ + uint32_t interval; /**< In milliseconds. Set to 0 for default.*/ + uint32_t count; /**< Set to 0 to flash until focused or cancelled.*/ +} PalFlashInfo; + +/** + * @struct PalIconCreateInfo + * @brief Creation parameters for an icon. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const uint8_t* pixels; /**< Pixels in `RGBA` format.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Height in pixels.*/ +} PalIconCreateInfo; + +/** + * @struct PalCursorCreateInfo + * @brief Creation parameters for a cursor. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const uint8_t* pixels; /**< Pixels in `RGBA` format.*/ + uint32_t width; /**< Width in pixels..*/ + uint32_t height; /**< Height in pixels.*/ + int32_t xHotspot; /**< X pixel for detecting clicks.*/ + int32_t yHotspot; /**< Y pixel for detecting clicks.*/ +} PalCursorCreateInfo; + +/** + * @struct PalWindowHandleInfo + * @brief Information about a window handle. + * + * @since 2.0 + */ +typedef struct { + void* nativeInstance; /**< The platform (OS) display or instance.*/ + void* nativeWindow; /**< The window platform (OS) handle.*/ + void* nativeHandle1; /**< Extra window handle (xdgSurface)*/ + void* nativeHandle2; /**< Extra window handle (xdgToplevel)*/ + void* nativeHandle3; /**< Extra window handle (wl_egl_window)*/ +} PalWindowHandleInfo; + +/** + * @struct PalWindowCreateInfo + * @brief Creation parameters for a window. + * + * Uninitialized fields may result in undefined behavior. + * + * @since 2.0 + */ +typedef struct { + const char* title; /**< Title in UTF-8 encoding.*/ + PalMonitor* monitor; /**< Set to `nullptr` to use primary monitor.*/ + const char* appName; /**< If `nullptr`, `PAL` will be used.*/ + const char* instanceName; /**< If `nullptr`, `title` will be used.*/ + PalFBConfigBackend fbConfigBackend; /**< Will be used if `fbConfigIndex` is not 0.*/ + int32_t fbConfigIndex; /**< Set to 0 to create the window with no pixel format.*/ + uint32_t width; /**< Width in pixels.*/ + uint32_t height; /**< Width in pixels.*/ + PalBool show; /**< Show after creation.*/ + PalWindowStyle style; /**< Window style.*/ + PalWindowState state; /**< (eg. `PAL_WINDOW_STATE_MAXIMIZED`).*/ + PalBool center; /**< Center after creation.*/ +} PalWindowCreateInfo; + +/** + * @brief Initialize the video system. + * + * This must be called before any video function. The video system must be + * shutdown with palShutdownVideo() when no longer needed. + * + * The allocator will not not copied, therefore the pointer must remain valid + * until the video system is shutdown. The event driver must be valid to recieve + * video events. + * + * If `preferredInstance` is `nullptr`, the video system creates one and control its lifetime. + * The provided instance will not be freed by the video system. + * `Linux`: This is the Display associated with the connection. + * `Windows`: This is the HINSTANCE of the process. + * + * @param[in] allocator Optional user-provided allocator. Set to `nullptr` to use + * default. + * @param[in] eventDriver Optional user-provided event driver. This is needed to + * push video events. Set to `nullptr` to use default. + * @param[in] preferredInstance User-provided instance (eg. HINSTANCE). Can be `nullptr`. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palShutdownVideo + */ +PAL_API PalResult PAL_CALL palInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance); + +/** + * @brief Shutdown the video system. + * + * If the video system has not been initialized, the function returns silently. + * All created windows, icons, and cursors must be destroyed before this call. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palInitVideo + */ +PAL_API void PAL_CALL palShutdownVideo(); + +/** + * @brief Update the video system and all created windows. + * + * If the video system has not been initialized, the function returns silently. + * This function pushes generated video events to the event driver set at + * palInitVideo(). + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palInitVideo + */ +PAL_API void PAL_CALL palUpdateVideo(); + +/** + * @brief Get the supported features of the video system. + * + * @return video features on success or `0` on failure. + * + * Thread safety: Thread safe. + * + * @since 2.0 + * @sa palInitVideo + */ +PAL_API PalVideoFeatures PAL_CALL palGetVideoFeatures(); + +/** + * @brief Return a list of all connected monitors. + * + * The video system must be initialized before this call. + * + * Call this function first with PalMonitor array set to `nullptr` to get the + * number of connected monitors. Allocate memory for the PalMonitor + * array and passed in the count and the allocated array. If the count of the + * array is less than the number of connected monitors, PAL will write upto that + * limit. + * + * The monitor handles must not be freed by the user, they are managed by the + * platform (OS). Users are required to cache this, and call this function again + * if monitors are added or removed. + * + * @param[in, out] count Capacity of the PalMonitor array. + * @param[out] outMonitors User allocated array of PalMonitor. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palGetPrimaryMonitor + */ +PAL_API PalResult PAL_CALL palEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitors); + +/** + * @brief Get the primary connected monitor. + * + * + * `PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY` must be supported otherwise undefined behavior. + * + * The monitor handle must not be freed by the user, they are managed by the + * platform (OS). + * + * @param[out] outMonitor Pointer to a PalMonitor to recieve the primary + * monitor. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palEnumerateMonitors + */ +PAL_API void PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor); + +/** + * @brief Get information about a monitor. + * + * + * + * This function takes in a PalMonitorInfo and fills it. + * Some of the fields are set to defaults if the operation is not supported on + * the platform (OS). example: On Windows 7, DPI will always be 96. + * + * @param[in] monitor Monitor to query information on. + * @param[out] info Pointer to a PalMonitorInfo to fill. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info); + +/** + * @brief Return a list of all supported monitor display modes for the provided + * monitor. + * + * + * + * Call this function first with PalMonitorMode array set to `nullptr` to get the + * number of supported monitor display modes. Allocate memory for the + * PalMonitorMode array and passed in the count and the allocated array. If the + * count of the array is less than the number of supported monitor display + * modes, PAL will write upto that limit. + * + * @param[in] monitor Monitor to query display modes on. + * @param[in, out] count Capacity of the PalMonitorMode array. + * @param[out] modes User allocated array of PalMonitorMode. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* modes); + +/** + * @brief Get the current monitor display mode of the provided monitor. + * + * + * `PAL_VIDEO_FEATURE_MONITOR_GET_MODE` must be supported otherwise undefined behavior. + * + * @param[in] monitor Monitor to query its current display mode. + * @param[out] mode Pointer to a PalMonitorMode to recieve the current monitor. + * mode. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palSetMonitorMode + */ +PAL_API void PAL_CALL palGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +/** + * @brief Set the active display monitor mode of the provided monitor. + * + * + * `PAL_VIDEO_FEATURE_MONITOR_SET_MODE` Must be supported otherwise undefined behavior. + * + * PAL only validates the monitor display mode pointer not the values. To be + * safe, users must get the monitor mode from palEnumerateMonitorModes() or call + * palValidateMonitorMode() to validate before switching. + * palValidateMonitorMode() is not supported on all platforms. + * + * If the monitor display mode submitted is invalid, this function might fail + * depending on the platform (OS). + * + * @param[in] monitor Monitor to set its current display mode. + * @param[in] mode Pointer to a PalMonitorMode to set. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palGetCurrentMonitorMode + */ +PAL_API PalResult PAL_CALL palSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +/** + * @brief Check if a monitor display mode is valid on the provided monitor. + * + * + * `PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE` must be supported otherwise undefined behavior. + * + * @param[in] monitor The monitor. + * @param[in] mode Pointer to a PalMonitorMode to validate. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +/** + * @brief Set the orientation for a monitor. + * + * + * `PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION` must be supported otherwise undefined behavior. + * + * This change is temporary and is reset when the platform (OS) reboots. + * + * @param[in] monitor Monitor to set its orientation. + * @param[in] orientation The orientation to set. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation); + +/** + * @brief Create a window. + * + * + * + * `PalWindowCreateInfo::fbConfigIndex` is the loop index from the drivers supported FBConfigs. + * `PalWindowCreateInfo::fbConfigBackend` is used to tell the video system the source of + * `PalWindowCreateInfo::fbConfigIndex`. + * + * Example Flow: + * Enumerate and select your FBConfig using any backend(EGL, GLX, WGL) + * and just let the video system know which one you used. + * + * If the backend passed is not the same as the one used, + * the video system might still get a FBConfig but it will not be the + * one requested. + * + * @param[in] info Pointer to a PalWindowCreateInfo struct that specifies parameters. + * @param[out] outWindow Pointer to a PalWindow to recieve the created window. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @note On Wayland + * + * - creating non resizable windows is not supported. + * PAL will always creating resizable windows. + * + * - Creating windows on a specific monitor is not supported. + * + * - Creating hidden window is not supported. It will be ignored. + * + * @since 2.0 + */ +PAL_API PalResult PAL_CALL palCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow); + +/** + * @brief Destroy the provided window. + * + * + * This only destroys windows created by PAL. + * + * @param[in] window Pointer to the window to destroy. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palCreateWindow + */ +PAL_API void PAL_CALL palDestroyWindow(PalWindow* window); + +/** + * @brief Minimize a maximized or restored window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported otherwise undefined behavior. + * If the window is already minimized, this functions does nothing. + * + * @param[in] window Window to minimize. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palMaximizeWindow + * @sa palRestoreWindow + */ +PAL_API void PAL_CALL palMinimizeWindow(PalWindow* window); + +/** + * @brief Maximize a minimized or restored window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported otherwise undefined behavior. + * If the window is already maximized, this functions does nothing. + * + * @param[in] window Window to maximize. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palMinimizeWindow + * @sa palRestoreWindow + */ +PAL_API void PAL_CALL palMaximizeWindow(PalWindow* window); + +/** + * @brief Restores a window to it previous state. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_STATE` must be supported otherwise undefined behavior. + * If the window is already restored, this functions does nothing. + * + * @param[in] window Window to restore. + * + * Thread safety: Must only be called from the main thread. + * + * @note Wayland does not support restoring a minimized windows. + * + * @since 2.0 + * @sa palMinimizeWindow + * @sa palMaximizeWindow + */ +PAL_API void PAL_CALL palRestoreWindow(PalWindow* window); + +/** + * @brief Show the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY` must be supported otherwise undefined behavior. + * All windows are created hidden if not explicitly shown. + * This does nothing if the window is already shown. + * + * @param[in] window Window to show. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palHideWindow + */ +PAL_API void PAL_CALL palShowWindow(PalWindow* window); + +/** + * @brief Hide the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY` must be supported otherwise undefined behavior. + * This does nothing if the window is already hidden. + * + * @param[in] window Window to hide. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palShowWindow + */ +PAL_API void PAL_CALL palHideWindow(PalWindow* window); + +/** + * @brief Request the platform (OS) to visually flash the provided window. + * + * + * + * If `PAL_FLASH_FLAG_CAPTION` is used, `PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION` must + * be supported. + * + * If `PAL_FLASH_FLAG_TRAY` is used, `PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY` must be + * supported. + * + * @param[in] window Pointer to the window. + * @param[in] info Pointer to a PalFlashInfo struct with flash parameters. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palFlashWindow( + PalWindow* window, + const PalFlashInfo* info); + +/** + * @brief Get the style of the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_STYLE` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] outStyle Pointer to a PalWindowStyle to recieve the window style. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palSetWindowStyle + */ +PAL_API void PAL_CALL palGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle); + +/** + * @brief Get the monitor the provided window is currently on. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] outMonitor Pointer to a PalMonitor to recieve the monitor. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor); + +/** + * @brief Get the title of the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_TITLE` must be supported otherwise undefined behavior. + * + * Set the buffer to `nullptr` to get the size of the window name in bytes. + * If the size of the provided buffer is less than the actual size of window + * title, PAL will write upto that limit. + * + * @param[in] window The window to query its title. + * @param[in] bufferSize Size of the provided buffer in bytes. + * @param[out] outSize The actual size of the window title in bytes. + * @param[out] outBuffer Pointer to a user provided buffer to recieve the title. + * Can be `nullptr`. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palSetWindowTitle + */ +PAL_API void PAL_CALL palGetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer); + +/** + * @brief Get the position of the provided window in pixels. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_POS` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] x Pointer to recieve the window x position. Can be `nullptr`. + * @param[out] y Pointer to recieve the window y position. Can be `nullptr`. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palSetWindowPos + */ +PAL_API void PAL_CALL palGetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +/** + * @brief Get the size of the provided window in pixels. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_SIZE` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] width Pointer to recieve the width. Can be `nullptr`. + * @param[out] height Pointer to recieve the height. Can be `nullptr`. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palSetWindowSize + */ +PAL_API void PAL_CALL palGetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height); + +/** + * @brief Get the state of the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_STATE` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] outState Pointer to a PalWindowState to recieve the window state. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetWindowState( + PalWindow* window, + PalWindowState* outState); + +/** + * @brief Get the state of the keycodes (layout aware keys) of the + * keyboard. + * + * + * + * The returned pointer must not be freed. The state is updated when + * palUpdateVideo() is called. The array must be index with PalKeycodes and + * not exceed `PAL_KEYCODE_COUNT`. + * + * @return A pointer to the keycodes array on success or `nullptr` on failure. + * + * Thread safety: Thread-safe. + * + * @since 2.0 + */ +PAL_API const PalBool* PAL_CALL palGetKeycodeState(); + +/** + * @brief Get the state of the scancodes (layout independent keys) of + * the keyboard. + * + * + * + * The returned pointer must not be freed. The state is updated when + * palUpdateVideo() is called. The array must be index with PalScancodes and + * not exceed PAL_SCANCODE_COUNT. + * + * @return A pointer to the scancodes array on success or `nullptr` on failure. + * + * Thread safety: Thread-safe. + * + * @since 2.0 + */ +PAL_API const PalBool* PAL_CALL palGetScancodeState(); + +/** + * @brief Get the state of the buttons of the mouse. + * + * + * + * The returned pointer must not be freed. The state is updated when + * palUpdateVideo() is called. The array must be index with PalMouseButton and + * not exceed `PAL_MOUSE_BUTTON_COUNT`. + * + * @return A pointer to the mouse button array on success or `nullptr` on failure. + * + * @Thread safety: Thread-safe. + * + * @since 2.0 + */ +PAL_API const PalBool* PAL_CALL palGetMouseState(); + +/** + * @brief Get the relative movement of the mouse in desktop pixels. + * + * + * The relative movement will be updated when palUpdateVideo() is called. + * + * @param[in] dx Pointer to recieve the mouse relative movement x. Can be + * `nullptr`. + * @param[in] dy Pointer to recieve the mouse relative movement y. Can be + * `nullptr`. + * + * Thread safety: Thread-safe if `dx` and `dy` are thread + * local. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetMouseDelta( + float* dx, + float* dy); + +/** + * @brief Get the wheel delta of the mouse. + * + * + * The wheel delta will be updated when palUpdateVideo() is called. + * + * @param[in] dx Pointer to recieve the mouse wheel delta x. Can be `nullptr`. + * @param[in] dy Pointer to recieve the mouse wheel delta y. Can be `nullptr`. + * + * Thread safety: Thread-safe if `dx` and `dy` are thread + * local. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetMouseWheelDelta( + float* dx, + float* dy); + +/** + * @brief Check if the provided window is visible. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * + * @return `true` if the window is visible otherwise `PAL_FALSE`. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalBool PAL_CALL palIsWindowVisible(PalWindow* window); + +/** + * @brief Get the current input-focused window per application. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS` must be supported otherwise undefined behavior. + * + * @return The current input-focused window on success or `nullptr` on + * failure. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API PalWindow* PAL_CALL palGetFocusWindow(); + +/** + * @brief Get the native handle of the provided window. + * + * + * + * On Wayland: `::nativeHandle1`, `::nativeHandle2` and `::nativeHandle3` + * are `xdg_surface`, `xdg_toplevel` and `wl_egl_window` respectively if available. + * + * @param[in] window Pointer to the window. + * @param[out] info Pointer to a PalWindowHandleInfo to fill. + * + * Thread safety: Thread-safe. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info); + +/** + * @brief Set the opacity of the provided window. + * + * + * `PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW` must be supported otherwise undefined behavior. + * The window must have `PAL_WINDOW_STYLE_TRANSPARENT` style. + * + * @param[in] window Pointer to the window. + * @param[in] opacity Must be in the range 0.0 - 1.0. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palSetWindowOpacity( + PalWindow* window, + float opacity); + +/** + * @brief Set the style of the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_STYLE` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[in] style The style to set. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetWindowStyle + */ +PAL_API void PAL_CALL palSetWindowStyle( + PalWindow* window, + PalWindowStyle style); + +/** + * @brief Set the title of the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_TITLE` must be supported otherwise undefined behavior. + * The title must be a UTF-8 encoding null terminated string. + * + * @param[in] window Pointer to the window. + * @param[in] title UTF-8 encoding null terminated string. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetWindowTitle + */ +PAL_API void PAL_CALL palSetWindowTitle( + PalWindow* window, + const char* title); + +/** + * @brief Set the position of the provided window in pixels. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_POS` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[in] x The new x coordinate in pixels. + * @param[in] y The new y coordinate in pixels. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetWindowPos + */ +PAL_API void PAL_CALL palSetWindowPos( + PalWindow* window, + int32_t x, + int32_t y); + +/** + * @brief Set the size of the provided window in pixels. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_SIZE` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[in] width The new width of the window in pixels. Must be greater than + * zero. + * @param[in] height The new height of the window in pixels. Must be greater + * than zero. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetWindowSize + */ +PAL_API void PAL_CALL palSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height); + +/** + * @brief Request input focus for the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetFocusWindow + */ +PAL_API void PAL_CALL palSetFocusWindow(PalWindow* window); + +/** + * @brief Create an icon. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported otherwise undefined behavior. + * + * @param[in] info Pointer to a PalIconCreateInfo struct that specifies parameters. + * @param[out] outIcon Pointer to a PalIcon to recieve the created icon. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palDestroyIcon + */ +PAL_API PalResult PAL_CALL palCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon); + +/** + * @brief Destroy the provided icon. + * + * + * + * @param[in] icon Pointer to the icon to destroy. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palCreateIcon + */ +PAL_API void PAL_CALL palDestroyIcon(PalIcon* icon); + +/** + * @brief Set the icon for the provided window. + * + * + * `PAL_VIDEO_FEATURE_WINDOW_SET_ICON` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[in] icon Pointer to the icon. Set to `nullptr` to revert. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palSetWindowIcon( + PalWindow* window, + PalIcon* icon); + +/** + * @brief Create a cursor. + * + * + * + * @param[in] info Pointer to a PalCursorCreateInfo struct that specifies parameters. + * @param[out] outCursor Pointer to a PalCursor to recieve the created cursor. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palDestroyCursor + */ +PAL_API PalResult PAL_CALL palCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor); + +/** + * @brief Create a system cursor. + * + * + * + * @param[in] type The system cursor type to create. + * @param[out] outCursor Pointer to a PalCursor to recieve the created cursor. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must only be called from the main thread. + * + * @since 2.0 + * @sa palDestroyCursor + */ +PAL_API PalResult PAL_CALL palCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor); + +/** + * @brief Destroy the provided cursor. + * + * + * + * @param[in] cursor Pointer to the cursor to destroy. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palCreateCursor + */ +PAL_API void PAL_CALL palDestroyCursor(PalCursor* cursor); + +/** + * @brief Show or hide the cursor. + * + * + * `PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY` must be supported otherwise undefined behavior. + * + * This affects all created cursors since the platform (OS) merges all cursors + * into a single one on the screen. + * + * @param[in] show True to make the cursor visible otherwise `PAL_FALSE`. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palShowCursor(PalBool show); + +/** + * @brief Clip the cursor to the provided window. + * + * + * `PAL_VIDEO_FEATURE_CLIP_CURSOR` must be supported otherwise undefined behavior. + * + * If the window is destroyed without unclipping the cursor, this cursor might + * not reset depending on the platform (OS). To be safe, unclip the cursor from + * the window before destroying the window. + * + * @param[in] window Pointer to the window. + * @param[in] clip True to clip to window or `PAL_FALSE` to unclip. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palClipCursor( + PalWindow* window, + PalBool clip); + +/** + * @brief Get the position of the cursor relative to the provided window in + * pixels. + * + * + * `PAL_VIDEO_FEATURE_CURSOR_GET_POS` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[out] x Pointer to recieve the x position. Can be + * `nullptr`. + * @param[out] y Pointer to recieve the y position. Can be + * `nullptr`. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palGetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +/** + * @brief Set the position of the cursor relative to the provided window in + * pixels. + * + * + * `PAL_VIDEO_FEATURE_CURSOR_SET_POS` must be supported otherwise undefined behavior. + * + * @param[in] window Pointer to the window. + * @param[in] x The new x coordinate of the cursor in pixels. + * @param[in] y The new y coordinate of the cursor in pixels. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palSetCursorPos( + PalWindow* window, + int32_t x, + int32_t y); + +/** + * @brief Set the cursor for the provided window. + * + * + * + * @param[in] window Pointer to the window. + * @param[in] cursor Pointer to the cursor. Set to `nullptr` to revert. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + */ +PAL_API void PAL_CALL palSetWindowCursor( + PalWindow* window, + PalCursor* cursor); + +/** + * @brief Get the native application instance or display. + * + * + * + * This returns the native instance or display of the application + * PAL video was initialized in. + * + * On Linux: This is the Display associated with the connection. + + * On Windows: This is the HINSTANCE of the process. + * + * @return The instance or display on success or `nullptr` on failure. + * + * Thread safety: Thread safe. + * + * @note The returned instance or display must not be freed. + * + * @since 2.0 + */ +PAL_API void* PAL_CALL palGetInstance(); + +/** + * @brief Attach a foreign or native window to PAL video system. + * + * + * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported otherwise undefined behavior. + * + * This function registers the provided window with PAL video system so it + * can manage events and use its functionality/API for the provided window. + * + * PAL does not own the window, it just sends events to that window. + * Users are responsible for destroying the window when no longer needed. + * palDestroyWindow() does not destroy the foreign or native window. + * + * Use Case: + * + * PAL takes your native foreign or native window and gives you a PalWindow + * which can be used with all of PAL API. The native window must be valid + * till the PalWindow has been detached with palDetachWindow(). + * + * The window must be created with the same instance or display + * that PAL uses. see palGetInstance(). + * + * @param[in] windowHandle Pointer to the foreign or native window. + * @param[out] outWindow Pointer to a PalWindow to recieve the attached window. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palGetInstance + * @sa palDestroyWindow + * @sa palDetachWindow + */ +PAL_API PalResult PAL_CALL palAttachWindow( + void* windowHandle, + PalWindow** outWindow); + +/** + * @brief Detach a foreign or native window from PAL video system. + * + * + * `PAL_VIDEO_FEATURE_FOREIGN_WINDOWS` must be supported otherwise undefined behavior. + * + * This function unregisters the provided window from PAL video system. + * The window must not be owned by PAL otherwise the function fails + * and return `PAL_RESULT_INVALID_WINDOW`. + * + * Detaching the window does not destroy the window, + * therefore destroying the window is the users responsibility. + * + * Use Case: + * + * Give back the PalWindow returned at palAttachWindow() + * and get back your native window. + * + * @param[in] window Pointer to the PalWindow to detach. + * @param[out] outWindowHandle Pointer to recieve the native window. Can be `nullptr`. + * + * @return `PAL_RESULT_SUCCESS` on success or a result code on + * failure. Call palFormatResult() for more information. + * + * Thread safety: Must be called from the main thread. + * + * @since 2.0 + * @sa palAttachWindow + */ +PAL_API PalResult PAL_CALL palDetachWindow( + PalWindow* window, + void** outWindowHandle); + +/** @} */ + +#endif // _PAL_VIDEO_H diff --git a/pal.lua b/pal.lua index ecf46819..d55405ae 100644 --- a/pal.lua +++ b/pal.lua @@ -1,90 +1,92 @@ dofile("pal_config.lua") -function writeConfig(path) - local file = io.open(path, "w") - file:write("\n// Auto Generated Config Header From pal_config.lua\n") - file:write("// Must not be edited manually\n\n") - - if (PAL_BUILD_SYSTEM) then - file:write("#define PAL_HAS_SYSTEM 1\n") - else - file:write("#define PAL_HAS_SYSTEM 0\n") - end - - if (PAL_BUILD_THREAD) then - file:write("#define PAL_HAS_THREAD 1\n") - else - file:write("#define PAL_HAS_THREAD 0\n") - end - - if (PAL_BUILD_VIDEO) then - file:write("#define PAL_HAS_VIDEO 1\n") - else - file:write("#define PAL_HAS_VIDEO 0\n") - end - - if (PAL_BUILD_OPENGL) then - file:write("#define PAL_HAS_OPENGL 1\n") - else - file:write("#define PAL_HAS_OPENGL 0\n") - end - - file:close() -end - -project "PAL" - language "C" - - if PAL_BUILD_STATIC then +project "PAL2" + if PAL_BUILD_STATIC_LIBRARY then kind "StaticLib" else kind "SharedLib" - defines { + defines { "_PAL_EXPORT", "_PAL_BUILD_DLL" } end - targetdir(target_dir) - objdir(obj_dir) + targetdir(targetDir) + objdir(objDir) includedirs { "include", "src" } - files { - "src/pal_core.c", - "src/pal_event.c" + files { + -- core + "src/core/pal_version.c", + + -- event + "src/event/pal_default_queue.c", + "src/event/pal_event.c" } - if (PAL_BUILD_SYSTEM) then + filter {"system:windows", "configurations:*"} + files { + "src/core/win32/pal_log_win32.c", + "src/core/win32/pal_memory_win32.c", + "src/core/win32/pal_result_win32.c", + "src/core/win32/pal_time_win32.c" + } + + filter {"system:linux", "configurations:*"} + files { + "src/core/posix/pal_log_posix.c", + "src/core/posix/pal_memory_posix.c", + "src/core/posix/pal_result_posix.c", + "src/core/posix/pal_time_posix.c" + } + + filter {} + + if (PAL_BUILD_SYSTEM_MODULE) then filter {"system:windows", "configurations:*"} - files { "src/system/pal_system_win32.c" } + files { + "src/system/win32/pal_cpu_win32.c", + "src/system/win32/pal_platform_win32.c" + } filter {"system:linux", "configurations:*"} - files { "src/system/pal_system_linux.c" } + files { + "src/system/linux/pal_cpu_linux.c", + "src/system/linux/pal_platform_linux.c" + } + filter {} end - if (PAL_BUILD_THREAD) then + if (PAL_BUILD_THREAD_MODULE) then filter {"system:windows", "configurations:*"} - files { "src/thread/pal_thread_win32.c" } + files { + "src/thread/win32/pal_condvar_win32.c", + "src/thread/win32/pal_mutex_win32.c", + "src/thread/win32/pal_tls_win32.c", + "src/thread/win32/pal_thread_win32.c" + } filter {"system:linux", "configurations:*"} - files { "src/thread/pal_thread_linux.c" } + files { + "src/thread/posix/pal_condvar_posix.c", + "src/thread/posix/pal_mutex_posix.c", + "src/thread/posix/pal_tls_posix.c", + "src/thread/posix/pal_thread_posix.c" + } filter {} end - if (PAL_BUILD_VIDEO) then - filter {"system:windows", "configurations:*"} - files { "src/video/pal_video_win32.c" } - - filter {"system:linux", "configurations:*"} - files { "src/video/pal_video_linux.c" } + if (PAL_BUILD_VIDEO_MODULE) then + files { "src/video/pal_video.c" } + if (os.target() == "linux") then -- check for wayland support. This is cross compiler local waylandPaths = { "/usr/include/wayland-client.h", @@ -102,12 +104,12 @@ project "PAL" end if found then - defines { "PAL_HAS_WAYLAND=1" } + defines { "PAL_HAS_WAYLAND_BACKEND=1" } else - defines { "PAL_HAS_WAYLAND=0" } + defines { "PAL_HAS_WAYLAND_BACKEND=0" } end - -- -- check for X11 support. This is cross compiler + -- check for X11 support. This is cross compiler local XPaths = { "/usr/include/X11/Xlib.h", "/usr/include/x86_64-linux-gnu/X11/Xlib.h" @@ -124,21 +126,146 @@ project "PAL" end if found then - defines { "PAL_HAS_X11=1" } + defines { "PAL_HAS_X11_BACKEND=1" } else - defines { "PAL_HAS_X11=0" } + defines { "PAL_HAS_X11_BACKEND=0" } end + end + + filter {"system:windows", "configurations:*"} + files { + "src/video/win32/pal_cursor_win32.c", + "src/video/win32/pal_icon_win32.c", + "src/video/win32/pal_monitor_win32.c", + "src/video/win32/pal_window_win32.c", + "src/video/win32/pal_video_win32.c" + } + + filter {"system:linux", "configurations:*"} + files { + -- X11 + "src/video/x11/pal_cursor_x11.c", + "src/video/x11/pal_icon_x11.c", + "src/video/x11/pal_monitor_x11.c", + "src/video/x11/pal_window_x11.c", + "src/video/x11/pal_video_x11.c", + + -- Wayland + "src/video/wayland/pal_cursor_wayland.c", + "src/video/wayland/pal_icon_wayland.c", + "src/video/wayland/pal_monitor_wayland.c", + "src/video/wayland/pal_window_wayland.c", + "src/video/wayland/pal_wayland_protocols.c", + "src/video/wayland/pal_video_wayland.c" + } filter {} end - if (PAL_BUILD_OPENGL) then + if (PAL_BUILD_OPENGL_MODULE) then + files { "src/opengl/pal_opengl.c" } + filter {"system:windows", "configurations:*"} - files { "src/opengl/pal_opengl_win32.c" } + files { + "src/opengl/wgl/pal_context_wgl.c", + "src/opengl/wgl/pal_wgl.c" + } filter {"system:linux", "configurations:*"} - files { "src/opengl/pal_opengl_linux.c" } + files { + "src/opengl/egl/pal_context_egl.c", + "src/opengl/egl/pal_egl.c" + } + filter {} end - writeConfig("include/pal/pal_config.h") \ No newline at end of file + if (PAL_BUILD_GRAPHICS_MODULE) then + -- check for vulkan support. This is cross compiler + local vulkanSdk = os.getenv("VULKAN_SDK") + local hasVulkan = false + if (vulkanSdk) then + hasVulkan = true + -- add to include path if compiler does not see it + includedirs { + path.join(vulkanSdk, "include") + } + + defines { "PAL_HAS_VULKAN_BACKEND=1" } + else + defines { "PAL_HAS_VULKAN_BACKEND=0" } + end + + -- check for d3d12 support. This is cross compiler + local hasD3D12 = false + local d3d12Include = "" + if (_ACTION == "vs2022") or (_ACTION == "vs2026") then + local base = "C:/Program Files (x86)/Windows Kits/10/Include" + local versions = os.matchdirs(base .. "/*") + table.sort(versions) + + for i = #versions, 1, -1 do + local v = versions[i] + d3d12Include = path.join(v, "um") + if (os.isdir(d3d12Include)) then + break + end + end + else + -- gccBasePath will be set if we are on gcc + d3d12Include = path.join(gccBasePath, "include") + end + + if (os.isfile(path.join(d3d12Include, "d3d12.h"))) then + hasD3D12 = true + end + + if (hasD3D12) then + -- add to include path if compiler does not see it + includedirs { + d3d12Include + } + + defines { "PAL_HAS_D3D12_BACKEND=1" } + else + defines { "PAL_HAS_D3D12_BACKEND=0" } + end + + -- base graphics file + files { "src/graphics/pal_graphics.c" } + if (hasVulkan) then + files { + "src/graphics/vulkan/pal_adapter_vulkan.c", + "src/graphics/vulkan/pal_as_vulkan.c", + "src/graphics/vulkan/pal_buffer_vulkan.c", + "src/graphics/vulkan/pal_command_pool_vulkan.c", + "src/graphics/vulkan/pal_commands_vulkan.c", + "src/graphics/vulkan/pal_descriptors_vulkan.c", + "src/graphics/vulkan/pal_device_vulkan.c", + "src/graphics/vulkan/pal_image_vulkan.c", + "src/graphics/vulkan/pal_pipeline_vulkan.c", + "src/graphics/vulkan/pal_sbt_vulkan.c", + "src/graphics/vulkan/pal_swapchain_vulkan.c", + "src/graphics/vulkan/pal_sync_vulkan.c", + "src/graphics/vulkan/pal_vulkan.c" + } + end + + if (hasD3D12) then + files { + "src/graphics/d3d12/pal_adapter_d3d12.c", + "src/graphics/d3d12/pal_as_d3d12.c", + "src/graphics/d3d12/pal_buffer_d3d12.c", + "src/graphics/d3d12/pal_command_pool_d3d12.c", + "src/graphics/d3d12/pal_commands_d3d12.c", + "src/graphics/d3d12/pal_descriptors_d3d12.c", + "src/graphics/d3d12/pal_device_d3d12.c", + "src/graphics/d3d12/pal_image_d3d12.c", + "src/graphics/d3d12/pal_pipeline_d3d12.c", + "src/graphics/d3d12/pal_sbt_d3d12.c", + "src/graphics/d3d12/pal_swapchain_d3d12.c", + "src/graphics/d3d12/pal_sync_d3d12.c", + "src/graphics/d3d12/pal_d3d12.c" + } + end + end diff --git a/pal_config.lua b/pal_config.lua index a912de31..ed26914e 100644 --- a/pal_config.lua +++ b/pal_config.lua @@ -1,18 +1,24 @@ -- build PAL as a static library -PAL_BUILD_STATIC = false +PAL_BUILD_STATIC_LIBRARY = false -- build PAL tests as a single application -PAL_BUILD_TESTS = true +PAL_BUILD_TEST_APPLICATION = true + +-- build PAL abi dump +PAL_BUILD_ABI_DUMP = true -- build system module -PAL_BUILD_SYSTEM = true +PAL_BUILD_SYSTEM_MODULE = true -- build thread module -PAL_BUILD_THREAD = true +PAL_BUILD_THREAD_MODULE = true -- build video module -PAL_BUILD_VIDEO = true +PAL_BUILD_VIDEO_MODULE = true -- build opengl module -PAL_BUILD_OPENGL = true \ No newline at end of file +PAL_BUILD_OPENGL_MODULE = true + +-- build graphics module +PAL_BUILD_GRAPHICS_MODULE = true diff --git a/premake/LICENSE.txt b/premake/LICENSE.txt index 66c3ad6f..e7611e64 100644 --- a/premake/LICENSE.txt +++ b/premake/LICENSE.txt @@ -24,4 +24,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/premake/premake5 b/premake/premake5 index 1b601b0a..b5354b58 100755 Binary files a/premake/premake5 and b/premake/premake5 differ diff --git a/premake/premake5.exe b/premake/premake5.exe index 1a637aa9..32f354f9 100644 Binary files a/premake/premake5.exe and b/premake/premake5.exe differ diff --git a/premake5.lua b/premake5.lua index c0aa049e..e077f905 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,10 +1,297 @@ dofile("pal_config.lua") -target_dir = "%{wks.location}/bin/%{cfg.buildcfg}" -obj_dir = "%{wks.location}/build" +targetDir = "%{wks.location}/bin/%{cfg.buildcfg}" +objDir = "%{wks.location}/build" -local ucrt = os.getenv("UCRT64") or "C:/msys64/ucrt64" +workspaceName = "PALWorkspace" +compilerPath = "" +intellisenseMode = "" +debuggerPath = "" +gccBasePath = "" + +local function getCommandOutput(cmd) + local result, exitCode = os.outputof(cmd) + if result then + result = result:gsub("[\r\n%s]+$", "") + + if (result ~= "") then + result = result:gsub("\\", "/") + return result + else + return nil + end + end + return nil +end + +local function removeDuplicates(list) + local seen = {} + local out = {} + + for _, v in ipairs(list) do + if not seen[v] then + seen[v] = true + table.insert(out, v) + end + end + + return out +end + +local function generateVscodeProperties() + print("\n=======================================================") + print("Generating .vscode/c_cpp_properties.json") + + local prjDefines = {} + local prjIncludes = {} + local workspace = premake.global.getWorkspace(workspaceName) + + for prj in premake.workspace.eachproject(workspace) do + for _, path in ipairs(prj.includedirs or {}) do + table.insert(prjIncludes, path) + end + + for _, define in ipairs(prj.defines or {}) do + table.insert(prjDefines, define) + end + end + + -- remove duplicates + prjIncludes = removeDuplicates(prjIncludes) + prjDefines = removeDuplicates(prjDefines) + + -- write to file + os.mkdir(".vscode") -- ensure .vscode directory exists + local file = io.open(".vscode/c_cpp_properties.json", "w") + if file then + file:write('{\n') + file:write(' "configurations": [\n') + file:write(' {\n') + file:write(string.format(' "name": "%s",\n', workspaceName)) + + -- includes + file:write(' "includePath": [\n') + for i, dir in ipairs(prjIncludes) do + file:write(string.format(' "%s"%s\n', dir, i < #prjIncludes and "," or "")) + end + file:write(' ],\n') + + -- defines + file:write(' "defines": [\n') + for i, define in ipairs(prjDefines) do + file:write(string.format(' "%s"%s\n', define, i < #prjDefines and "," or "")) + end + file:write(' ],\n') + + file:write(string.format(' "compilerPath": "%s",\n', compilerPath)) + file:write(string.format(' "intelliSenseMode": "%s",\n', intellisenseMode)) + file:write(' "cStandard": "c99"\n') + + file:write(' }\n') + file:write(' ],\n') + file:write(' "version": 4\n') + file:write('}\n') + + file:close() + end +end + +local function writeTasksConfiguration(file, actionType) + local name = "" + local isDefault = "false" + local command = "" + + if actionType == "buildDebug" then + name = "build debug" + command = "make all config=debug" + isDefault = "true" + + elseif actionType == "buildRelease" then + name = "build release" + command = "make all config=release" + + elseif actionType == "cleanDebug" then + name = "clean debug" + command = "make clean config=debug" + + elseif actionType == "cleanRelease" then + name = "clean release" + command = "make clean config=release" + end + + file:write(" {\n") + file:write(' "type": "shell",\n') + file:write(string.format(' "label": "%s %s",\n', workspaceName, name)) + file:write(string.format(' "command": "%s",\n', command)) + + file:write(' "options": {\n') + file:write(' "cwd": "${workspaceFolder}"\n') + file:write(' },\n') + + file:write(' "problemMatcher": [\n') + file:write(' "$gcc",\n') + file:write(' ],\n') + + file:write(' "group": {\n') + file:write(' "kind": "build",\n') + file:write(string.format(' "isDefault": %s\n', isDefault)) + file:write(' }\n') +end + +local function generateTasksJson() + print("\n=======================================================") + print("Generating .vscode/tasks.json") + + local file = io.open(".vscode/tasks.json", "w") + if file then + file:write('{\n') + file:write(' "tasks": [\n') + + writeTasksConfiguration(file, "buildDebug") + file:write(" },\n") + file:write('\n') + + writeTasksConfiguration(file, "buildRelease") + file:write(" },\n") + file:write('\n') + + -- clean configurations + writeTasksConfiguration(file, "cleanDebug") + file:write(" },\n") + file:write('\n') + + writeTasksConfiguration(file, "cleanRelease") + file:write(" }\n") + + file:write(" ],\n") + file:write(' "version": "2.0.0"\n') + file:write("}\n") + + file:close() + end +end + +local function writeLaunchConfiguration(file, app, isDebug) + local name = "" + local preLaunchTask = "" + local dir = "" + local cwd = "" + + if isDebug then + name = "launch debug" + preLaunchTask = "build debug" + dir = "Debug" + else + name = "launch release" + preLaunchTask = "build release" + dir = "Release" + end + + if app == "tests" then + cwd = "tests" + if isDebug then + name = "launch tests debug" + else + name = "launch tests release" + end + + if os.target() == "windows" then + program = "tests.exe" + else + program = "tests" + end + + else + cwd = "tools/abi_dump" + if isDebug then + name = "launch abi-dump debug" + else + name = "launch abi-dump release" + end + + if os.target() == "windows" then + program = "abi-dump.exe" + else + program = "abi-dump" + end + end + + file:write(" {\n") + file:write(string.format(' "name": "%s %s",\n', workspaceName, name)) + file:write(' "type": "cppdbg",\n') + file:write(' "request": "launch",\n') + file:write(' "stopAtEntry": false,\n') + file:write(string.format(' "cwd": "${workspaceFolder}/%s",\n', cwd)) + + file:write(' "environment": [],\n') + file:write(' "externalConsole": false,\n') + file:write(string.format(' "preLaunchTask": "%s %s",\n', workspaceName, preLaunchTask)) + file:write(string.format(' "program": "${workspaceFolder}/bin/%s/%s",\n', dir, program)) + file:write(' "MIMode": "gdb",\n') + file:write(string.format(' "miDebuggerPath": "%s",\n', debuggerPath)) + + if isDebug then + file:write(' "setupCommands": [\n') + + file:write(' {\n') + file:write(' "description": "Enable pretty printing for gdb",\n') + file:write(' "text": "-enable-pretty-printing",\n') + file:write(' "ignoreFailures": false,\n') + file:write(' },\n') + + file:write(' {\n') + file:write(' "description": "Set disassembly flavor to intel",\n') + file:write(' "text": "-gdb-set disassembly-flavor intel",\n') + file:write(' "ignoreFailures": false,\n') + file:write(' }\n') + + file:write(' ]\n') + end +end + +local function generateLaunchJson() + print("\n=======================================================") + print("Generating .vscode/launch.json") + + local file = io.open(".vscode/launch.json", "w") + if file then + file:write('{\n') + file:write(' "configurations": [\n') + + writeLaunchConfiguration(file, "tests", true) + file:write(" },\n") + file:write('\n') + + writeLaunchConfiguration(file, "tests", false) + file:write(" },\n") + file:write('\n') + + writeLaunchConfiguration(file, "abi-dump", true) + file:write(" },\n") + file:write('\n') + + writeLaunchConfiguration(file, "abi-dump", false) + file:write(" }\n") + + file:write(" ],\n") + file:write(' "version": "0.2.0"\n') + file:write("}\n") + file:close() + end +end + +-- generate vscode properties if using gmake +premake.override(premake.action, "call", function(base, action) + base(action) + + if action == "gmake" then + generateVscodeProperties() + generateTasksJson() + generateLaunchJson() + end + +end) newoption { trigger = "compiler", @@ -17,29 +304,29 @@ newoption { } } -workspace "PAL_workspace" - if PAL_BUILD_TESTS then +workspace(workspaceName) + if PAL_BUILD_TEST_APPLICATION then startproject("tests") + else + startproject("abi-dump") end - if PAL_BUILD_STATIC then + if PAL_BUILD_STATIC_LIBRARY then staticruntime "on" else staticruntime "off" end - + + multiprocessorcompile "On" + cdialect "C99" + architecture "x64" + language "C" + configurations { "Debug", "Release" } - flags { "MultiProcessorCompile" } filter {"system:windows", "configurations:*"} - architecture "x64" systemversion "latest" - cdialect "C99" - filter {"system:linux", "configurations:*"} - architecture "x86_64" - cdialect "C99" - filter "configurations:Debug" symbols "on" runtime "Debug" @@ -51,45 +338,116 @@ workspace "PAL_workspace" filter {} - if (_ACTION == "gmake2") then - if (_OPTIONS["compiler"] == "clang") then - toolset("clang") + if (_ACTION == "gmake") then + if os.target() == "windows" then + local gccPath = getCommandOutput("where gcc.exe 2>nul") + local gccBinPath = path.getdirectory(gccPath) + gccBasePath = path.getdirectory(gccBinPath) + debuggerPath = getCommandOutput("where gdb.exe 2>nul") - buildoptions { - "-target x86_64-w64-windows-gnu", - "-I" .. ucrt .. "/include", - "-I" .. ucrt .. "/ucrt/include", - "-I" .. ucrt .. "/mingw/include", + if (_OPTIONS["compiler"] == "clang") then + toolset("clang") + + buildoptions { + "-target x86_64-w64-windows-gnu", + "-I" .. gccBasePath .. "/include", + "-I" .. gccBasePath .. "/ucrt/include", + "-I" .. gccBasePath .. "/mingw/include", + + -- warnings + "-Wno-switch", -- for switch statements + "-Wno-switch-enum" -- for switch statements + } - -- warnings - "-Wno-switch", -- for switch statements - "-Wno-switch-enum" -- for switch statements - } - - linkoptions { - "-target x86_64-w64-windows-gnu", - "-L" .. ucrt .. "/lib", - "-L" .. ucrt .. "/mingw/lib" - } + linkoptions { + "-target x86_64-w64-windows-gnu", + "-L" .. gccBasePath .. "/lib", + "-L" .. gccBasePath .. "/mingw/lib" + } + + intellisenseMode = "windows-clang-x64" + compilerPath = getCommandOutput("where clang.exe 2>nul") + else + -- GCC + intellisenseMode = "gcc-x64" + compilerPath = gccPath + end + else + -- linux + if (_OPTIONS["compiler"] == "clang") then + toolset("clang") + + buildoptions { + -- warnings + "-Wno-switch", -- for switch statements + "-Wno-switch-enum" -- for switch statements + } + + intellisenseMode = "linux-clang-x64" + compilerPath = "/usr/bin/clang" + else + -- GCC + intellisenseMode = "linux-gcc-x64" + compilerPath = "/usr/bin/gcc" + end + + debuggerPath = "/usr/bin/gdb" end end - if (_ACTION == "vs2022") then - if (_OPTIONS["compiler"] == "clang") then + if (_ACTION == "vs2022") or (_ACTION == "vs2026") then + if (_OPTIONS["compiler"] == "clang") then toolset("clang") end defines { "_CRT_SECURE_NO_WARNINGS" } + disablewarnings { - "6387" + "6387", + "4018", + "4133", + "4101" } end - if (PAL_BUILD_TESTS) then + if (PAL_BUILD_SYSTEM_MODULE) then + defines { "PAL_HAS_SYSTEM_MODULE=1" } + else + defines { "PAL_HAS_SYSTEM_MODULE=0" } + end + + if (PAL_BUILD_THREAD_MODULE) then + defines { "PAL_HAS_THREAD_MODULE=1" } + else + defines { "PAL_HAS_THREAD_MODULE=0" } + end + + if (PAL_BUILD_VIDEO_MODULE) then + defines { "PAL_HAS_VIDEO_MODULE=1" } + else + defines { "PAL_HAS_VIDEO_MODULE=0" } + end + + if (PAL_BUILD_OPENGL_MODULE) then + defines { "PAL_HAS_OPENGL_MODULE=1" } + else + defines { "PAL_HAS_OPENGL_MODULE=0" } + end + + if (PAL_BUILD_GRAPHICS_MODULE) then + defines { "PAL_HAS_GRAPHICS_MODULE=1" } + else + defines { "PAL_HAS_GRAPHICS_MODULE=0" } + end + + if (PAL_BUILD_TEST_APPLICATION) then include "tests/tests.lua" end - include "pal.lua" - \ No newline at end of file + if (PAL_BUILD_ABI_DUMP) then + include "tools/abi_dump/abi_dump.lua" + end + + include "pal.lua" \ No newline at end of file diff --git a/src/core/pal_format.h b/src/core/pal_format.h new file mode 100644 index 00000000..4256c062 --- /dev/null +++ b/src/core/pal_format.h @@ -0,0 +1,42 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_FORMAT_H +#define _PAL_FORMAT_H + +#include "pal2/pal_core.h" +#include +#include + +static inline void formatArgs( + const char* fmt, + va_list argsList, + char* buffer) +{ + va_list argsListCopy; + va_copy(argsListCopy, argsList); + int len = vsnprintf(nullptr, 0, fmt, argsListCopy); + va_end(argsListCopy); + + va_copy(argsListCopy, argsList); + vsnprintf(buffer, len + 1, fmt, argsListCopy); + va_end(argsListCopy); + buffer[len] = 0; +} + +static inline void format( + char* buffer, + const char* fmt, + ...) +{ + va_list argPtr; + va_start(argPtr, fmt); + formatArgs(fmt, argPtr, buffer); + va_end(argPtr); +} + +#endif // _PAL_FORMAT_H \ No newline at end of file diff --git a/src/core/pal_result.h b/src/core/pal_result.h new file mode 100644 index 00000000..3ed5b28b --- /dev/null +++ b/src/core/pal_result.h @@ -0,0 +1,135 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_RESULT_H +#define _PAL_RESULT_H + +#include "pal_format.h" + +static const char* resultCodeToString(PalResult result) +{ + PalResultCode code = palGetResultCode(result); + switch (code) { + case PAL_RESULT_CODE_INVALID_ARGUMENT: + return "PAL_RESULT_CODE_INVALID_ARGUMENT"; + + case PAL_RESULT_CODE_OUT_OF_MEMORY: + return "PAL_RESULT_CODE_OUT_OF_MEMORY"; + + case PAL_RESULT_CODE_PLATFORM_FAILURE: + return "PAL_RESULT_CODE_PLATFORM_FAILURE"; + + case PAL_RESULT_CODE_TIMEOUT: + return "PAL_RESULT_CODE_TIMEOUT"; + + case PAL_RESULT_CODE_INVALID_HANDLE: + return "PAL_RESULT_CODE_INVALID_HANDLE"; + + case PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED: + return "PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED"; + + case PAL_RESULT_CODE_INVALID_OPERATION: + return "PAL_RESULT_CODE_INVALID_OPERATION"; + + case PAL_RESULT_CODE_DEVICE_LOST: + return "PAL_RESULT_CODE_DEVICE_LOST"; + + case PAL_RESULT_CODE_OUT_OF_DATE: + return "PAL_RESULT_CODE_OUT_OF_DATE"; + } + + return ""; +} + +static const char* resultCodeToDescription(PalResult result) +{ + PalResultCode code = palGetResultCode(result); + switch (code) { + case PAL_RESULT_CODE_INVALID_ARGUMENT: + return "Invalid argument"; + + case PAL_RESULT_CODE_OUT_OF_MEMORY: + return "Out of memory"; + + case PAL_RESULT_CODE_PLATFORM_FAILURE: + return "Platform failure"; + + case PAL_RESULT_CODE_TIMEOUT: + return "Timeout"; + + case PAL_RESULT_CODE_INVALID_HANDLE: + return "Invalid handle"; + + case PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED: + return "Feature not supported"; + + case PAL_RESULT_CODE_INVALID_OPERATION: + return "Invalid operation"; + + case PAL_RESULT_CODE_DEVICE_LOST: + return "Device lost"; + + case PAL_RESULT_CODE_OUT_OF_DATE: + return "Out of date"; + } + + return nullptr; +} + +static const char* resultSourceToString(PalResult result) +{ + PalResultSource source = palGetResultSource(result); + switch (source) { + case PAL_RESULT_SOURCE_WIN32: + return "WIN32"; + + case PAL_RESULT_SOURCE_POSIX: + return "POSIX"; + + case PAL_RESULT_SOURCE_EGL: + return "EGL"; + + case PAL_RESULT_SOURCE_VULKAN: + return "VULKAN"; + + case PAL_RESULT_SOURCE_D3D12: + return "D3D12"; + + case PAL_RESULT_SOURCE_METAL: + return "METAL"; + } + + return "NONE"; +} + +static void formatResultMsg( + PalResult result, + char* buffer, + char* msg) +{ + const char* baseString = resultCodeToString(result); + const char* sourceString = resultSourceToString(result); + const char* baseDescription = resultCodeToDescription(result); + uint32_t nativeCode = palGetResultNativeCode(result); + + const char* description = ""; + if (msg) { + description = msg; + } + + format( + buffer, + "Source: %s\n PAL Code: %s\n Native Code: 0x%08x\n PAL Description: %s\n Native " + "Description: %s", + sourceString, + baseString, + nativeCode, + baseDescription, + description); +} + +#endif // _PAL_RESULT_H \ No newline at end of file diff --git a/src/core/pal_version.c b/src/core/pal_version.c new file mode 100644 index 00000000..361cd8bb --- /dev/null +++ b/src/core/pal_version.c @@ -0,0 +1,25 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal2/pal_core.h" + +#define PAL_VERSION_MAJOR 2 +#define PAL_VERSION_MINOR 0 +#define PAL_VERSION_BUILD 0 +#define PAL_VERSION_STRING "2.0.0" + +void PAL_CALL palGetVersion(PalVersion* version) +{ + version->major = PAL_VERSION_MAJOR; + version->minor = PAL_VERSION_MINOR; + version->build = PAL_VERSION_BUILD; +} + +const char* PAL_CALL palGetVersionString() +{ + return PAL_VERSION_STRING; +} \ No newline at end of file diff --git a/src/core/posix/pal_log_posix.c b/src/core/posix/pal_log_posix.c new file mode 100644 index 00000000..1984eeab --- /dev/null +++ b/src/core/posix/pal_log_posix.c @@ -0,0 +1,87 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#include "core/pal_format.h" +#include +#include + +#define MSG_SIZE 4096 + +typedef struct { + char tmp[MSG_SIZE]; + char buffer[MSG_SIZE]; + PalBool isLogging; +} LogTLSData; + +pthread_key_t s_TLSID = 0; +static pthread_once_t s_TLSCreation = PTHREAD_ONCE_INIT; + +static void destroyTlsData(void* data) +{ + LogTLSData* tlsData = data; + if (tlsData) { + palFree(nullptr, tlsData); + } +} + +void createTLSID() +{ + if (pthread_key_create(&s_TLSID, destroyTlsData) != 0) { + return; + } +} + +void PAL_CALL palLog( + const PalLogger* logger, + const char* fmt, + ...) +{ + LogTLSData* data = pthread_getspecific(s_TLSID); + if (!data) { + data = palAllocate(nullptr, sizeof(LogTLSData), 0); + memset(data, 0, sizeof(LogTLSData)); + + // create TLS if it has not been created + pthread_once(&s_TLSCreation, createTLSID); + pthread_setspecific(s_TLSID, data); + } + + va_list argPtr; + va_start(argPtr, fmt); + formatArgs(fmt, argPtr, data->tmp); + va_end(argPtr); + + // check to see if a user supplied a logger + if (logger && logger->callback) { + if (data->isLogging) { + // block recursion + return; + } + + // update the tls to stop recursive calls + memcpy(data->buffer, data->tmp, MSG_SIZE); + data->isLogging = PAL_TRUE; + pthread_setspecific(s_TLSID, data); + logger->callback(logger->userData, data->buffer); + + } else { + // add newline character to the string + format(data->buffer, "%s\n", data->tmp); + + // write to console + fprintf(stdout, "%s", data->buffer); + fflush(stdout); + } + + data->isLogging = PAL_FALSE; + pthread_setspecific(s_TLSID, data); +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/core/posix/pal_memory_posix.c b/src/core/posix/pal_memory_posix.c new file mode 100644 index 00000000..e4f31ac6 --- /dev/null +++ b/src/core/posix/pal_memory_posix.c @@ -0,0 +1,45 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#define _POSIX_C_SOURCE 200112L +#include "pal2/pal_core.h" +#include + +void* PAL_CALL palAllocate( + const PalAllocator* allocator, + uint64_t size, + uint64_t alignment) +{ + uint64_t align = alignment; + if (align == 0) { + align = 16; // default + } + + if (allocator) { + return allocator->allocate(allocator->userData, size, align); + } + + void* ptr = nullptr; + posix_memalign(&ptr, align, size); + return ptr; +} + +void PAL_CALL palFree( + const PalAllocator* allocator, + void* ptr) +{ + if (allocator) { + allocator->free(allocator->userData, ptr); + } else { + free(ptr); + } +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/core/posix/pal_result_posix.c b/src/core/posix/pal_result_posix.c new file mode 100644 index 00000000..b48eeb8a --- /dev/null +++ b/src/core/posix/pal_result_posix.c @@ -0,0 +1,29 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#define _POSIX_C_SOURCE 200112L +#include "core/pal_format.h" +#include "core/pal_result.h" +#include + +void PAL_CALL palFormatResult( + PalResult result, + uint64_t bufferSize, + char* buffer) +{ + char tmpBuffer[256]; + uint32_t nativeCode = palGetResultNativeCode(result); + if (nativeCode != 0) { + strerror_r(nativeCode, tmpBuffer, 256); + formatResultMsg(result, buffer, tmpBuffer); + } +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/core/posix/pal_time_posix.c b/src/core/posix/pal_time_posix.c new file mode 100644 index 00000000..fcf979ae --- /dev/null +++ b/src/core/posix/pal_time_posix.c @@ -0,0 +1,27 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#define _POSIX_C_SOURCE 200112L +#include "pal2/pal_core.h" +#include + +uint64_t PAL_CALL palGetPerformanceCounter() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000LL + (uint64_t)ts.tv_nsec; +} + +uint64_t PAL_CALL palGetPerformanceFrequency() +{ + return 1000000000LL; +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/core/win32/pal_log_win32.c b/src/core/win32/pal_log_win32.c new file mode 100644 index 00000000..f61d99e2 --- /dev/null +++ b/src/core/win32/pal_log_win32.c @@ -0,0 +1,106 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "core/pal_format.h" +#include +#include + +#define MSG_SIZE 4096 + +typedef struct { + char tmp[MSG_SIZE]; + char buffer[MSG_SIZE]; + wchar_t wideBuffer[MSG_SIZE]; + PalBool isLogging; +} LogTLSData; + +static volatile LONG s_TlsID = 0; + +static void destroyTlsData(void* data) +{ + LogTLSData* tlsData = data; + if (tlsData) { + palFree(nullptr, tlsData); + } +} + +void PAL_CALL palLog( + const PalLogger* logger, + const char* fmt, + ...) +{ + LogTLSData* data = FlsGetValue((DWORD)s_TlsID); + if (!data) { + data = palAllocate(nullptr, sizeof(LogTLSData), 0); + memset(data, 0, sizeof(LogTLSData)); + + // create TLS if it has not been created + if (s_TlsID == 0) { + DWORD TLSIndex = FlsAlloc(destroyTlsData); + // update the TLS using atomic operations to avoid thread race + LONG prev = InterlockedCompareExchange((volatile LONG*)&s_TlsID, (LONG)TLSIndex, 0); + if (prev != 0) { + // Another thread has already set this, + // destroy the tls index + FlsFree(TLSIndex); + } + } + FlsSetValue(s_TlsID, data); + } + + va_list argPtr; + va_start(argPtr, fmt); + formatArgs(fmt, argPtr, data->tmp); + va_end(argPtr); + + // check to see if a user supplied a logger + if (logger && logger->callback) { + if (data->isLogging) { + // block recursion + return; + } + + // update the tls to stop recursive calls + memcpy(data->buffer, data->tmp, MSG_SIZE); + data->isLogging = PAL_TRUE; + FlsSetValue(s_TlsID, data); + logger->callback(logger->userData, data->buffer); + + } else { + // add newline character to the string + format(data->buffer, "%s\n", data->tmp); + + // write to console + HANDLE console = GetStdHandle(STD_ERROR_HANDLE); + int len = MultiByteToWideChar(CP_UTF8, 0, data->buffer, -1, nullptr, 0); + MultiByteToWideChar(CP_UTF8, 0, data->buffer, -1, data->wideBuffer, len); + if (console) { + WriteConsoleW(console, data->wideBuffer, (DWORD)len - 1, NULL, 0); + } else { + OutputDebugStringW(data->wideBuffer); + } + } + + data->isLogging = PAL_FALSE; + FlsSetValue(s_TlsID, data); +} + +#endif // _WIN32 diff --git a/src/core/win32/pal_memory_win32.c b/src/core/win32/pal_memory_win32.c new file mode 100644 index 00000000..0a65ce42 --- /dev/null +++ b/src/core/win32/pal_memory_win32.c @@ -0,0 +1,43 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal2/pal_core.h" + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include +#endif // _MSC_VER + +void* PAL_CALL palAllocate( + const PalAllocator* allocator, + uint64_t size, + uint64_t alignment) +{ + uint64_t align = alignment; + if (align == 0) { + align = 16; // default + } + + if (allocator) { + return allocator->allocate(allocator->userData, size, align); + } + + return _aligned_malloc(size, align); +} + +void PAL_CALL palFree( + const PalAllocator* allocator, + void* ptr) +{ + if (allocator) { + allocator->free(allocator->userData, ptr); + } else { + _aligned_free(ptr); + } +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/core/win32/pal_result_win32.c b/src/core/win32/pal_result_win32.c new file mode 100644 index 00000000..006f2faa --- /dev/null +++ b/src/core/win32/pal_result_win32.c @@ -0,0 +1,48 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "core/pal_format.h" +#include "core/pal_result.h" +#include +#include + +void PAL_CALL palFormatResult( + PalResult result, + uint64_t bufferSize, + char* buffer) +{ + char tmpBuffer[256]; + uint32_t nativeCode = palGetResultNativeCode(result); + if (nativeCode != 0) { + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + nativeCode, + 0, + tmpBuffer, + 256, + nullptr); + + formatResultMsg(result, buffer, tmpBuffer); + } +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/core/win32/pal_time_win32.c b/src/core/win32/pal_time_win32.c new file mode 100644 index 00000000..a8981241 --- /dev/null +++ b/src/core/win32/pal_time_win32.c @@ -0,0 +1,39 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_core.h" +#include + +uint64_t PAL_CALL palGetPerformanceCounter() +{ + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (uint64_t)counter.QuadPart; +} + +uint64_t PAL_CALL palGetPerformanceFrequency() +{ + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return (uint64_t)frequency.QuadPart; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/event/pal_default_queue.c b/src/event/pal_default_queue.c new file mode 100644 index 00000000..87b4822f --- /dev/null +++ b/src/event/pal_default_queue.c @@ -0,0 +1,69 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_default_queue.h" + +typedef struct { + uint32_t head; + uint32_t tail; + PalEvent data[MAX_EVENTS]; +} QueueData; + +static void PAL_CALL eventPush( + void* queue, + PalEvent* event) +{ + PalEventQueue* eventQueue = queue; + QueueData* data = eventQueue->userData; + data->data[data->tail++ % MAX_EVENTS] = *event; +} + +static PalBool PAL_CALL eventPoll( + void* queue, + PalEvent* outEvent) +{ + PalEventQueue* eventQueue = queue; + QueueData* data = eventQueue->userData; + if (data->head == data->tail) { + return PAL_FALSE; + } + + *outEvent = data->data[data->head++ % MAX_EVENTS]; + return PAL_TRUE; +} + +PalEventQueue* createDefaultEventQueue(const PalAllocator* allocator) +{ + PalEventQueue* queue = nullptr; + queue = palAllocate(allocator, sizeof(PalEventQueue), 0); + if (!queue) { + return nullptr; + } + + // we create a default event queue data + QueueData* queueData = nullptr; + queueData = palAllocate(allocator, sizeof(QueueData), 0); + if (!queueData) { + palFree(allocator, queue); + return nullptr; + } + + memset(queueData, 0, sizeof(QueueData)); + queue->userData = queueData; + queue->poll = eventPoll; + queue->push = eventPush; + + return queue; +} + +void destroyDefaultEventQueue( + const PalAllocator* allocator, + PalEventQueue* queue) +{ + palFree(allocator, queue->userData); + palFree(allocator, queue); +} \ No newline at end of file diff --git a/src/event/pal_default_queue.h b/src/event/pal_default_queue.h new file mode 100644 index 00000000..bc271c6d --- /dev/null +++ b/src/event/pal_default_queue.h @@ -0,0 +1,21 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_DEFAULT_QUEUE_H +#define _PAL_DEFAULT_QUEUE_H + +#include "pal2/pal_event.h" + +#define MAX_EVENTS 512 + +PalEventQueue* createDefaultEventQueue(const PalAllocator* allocator); + +void destroyDefaultEventQueue( + const PalAllocator* allocator, + PalEventQueue* queue); + +#endif // _PAL_DEFAULT_QUEUE_H \ No newline at end of file diff --git a/src/event/pal_event.c b/src/event/pal_event.c new file mode 100644 index 00000000..2f0c2c33 --- /dev/null +++ b/src/event/pal_event.c @@ -0,0 +1,106 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_default_queue.h" +#include + +struct PalEventDriver { + PalBool freeQueue; + PalEventQueue* queue; + const PalAllocator* allocator; + PalEventCallback callback; + void* userData; + PalDispatchMode modes[MAX_EVENTS]; +}; + +PalResult PAL_CALL palCreateEventDriver( + const PalEventDriverCreateInfo* info, + PalEventDriver** outEventDriver) +{ + if (!info || !outEventDriver) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalEventDriver* driver = nullptr; + driver = palAllocate(info->allocator, sizeof(PalEventDriver), 0); + if (!driver) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(driver, 0, sizeof(PalEventDriver)); + if (info->allocator) { + driver->allocator = info->allocator; + } + + if (info->queue) { + // user supplied an event queue + driver->queue = info->queue; + driver->freeQueue = PAL_FALSE; + + } else { + // we create a default event queue + PalEventQueue* queue = createDefaultEventQueue(info->allocator); + if (!queue) { + palFree(info->allocator, driver); + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + driver->queue = queue; + driver->freeQueue = PAL_TRUE; + } + + driver->callback = info->callback; + driver->userData = info->userData; + *outEventDriver = driver; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyEventDriver(PalEventDriver* eventDriver) +{ + const PalAllocator* allocator = eventDriver->allocator; + if (eventDriver->freeQueue) { + destroyDefaultEventQueue(allocator, eventDriver->queue); + } + palFree(allocator, eventDriver); +} + +void PAL_CALL palSetEventDispatchMode( + PalEventDriver* eventDriver, + PalEventType type, + PalDispatchMode mode) +{ + eventDriver->modes[type] = mode; +} + +PalDispatchMode PAL_CALL palGetEventDispatchMode( + PalEventDriver* eventDriver, + PalEventType type) +{ + return eventDriver->modes[type]; +} + +void PAL_CALL palPushEvent( + PalEventDriver* eventDriver, + PalEvent* event) +{ + PalDispatchMode mode = eventDriver->modes[event->type]; + if (mode == PAL_DISPATCH_MODE_CALLBACK) { + eventDriver->callback(eventDriver->userData, event); + return; // we have dispatched the event + } + + if (mode == PAL_DISPATCH_MODE_POLL) { + eventDriver->queue->push(eventDriver->queue, event); + } +} + +PalBool PAL_CALL palPollEvent( + PalEventDriver* eventDriver, + PalEvent* outEvent) +{ + return eventDriver->queue->poll(eventDriver->queue, outEvent); +} diff --git a/src/graphics/d3d12/pal_adapter_d3d12.c b/src/graphics/d3d12/pal_adapter_d3d12.c new file mode 100644 index 00000000..a7046e9a --- /dev/null +++ b/src/graphics/d3d12/pal_adapter_d3d12.c @@ -0,0 +1,639 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +// on older SDKs, D3D_FEATURE_LEVEL_12_2 is not defined +#ifndef D3D_FEATURE_LEVEL_12_2 +#define D3D_FEATURE_LEVEL_12_2 0xc200 +#endif // D3D_FEATURE_LEVEL_12_2 + +// From Agility SDK +#ifndef D3D_SHADER_MODEL_6_8 +#define D3D_SHADER_MODEL_6_8 0x68 +#endif // D3D_SHADER_MODEL_6_8 + +#ifndef D3D_SHADER_MODEL_6_9 +#define D3D_SHADER_MODEL_6_9 0x69 +#endif // D3D_SHADER_MODEL_6_9 + +#ifndef D3D_SHADER_MODEL_6_10 +#define D3D_SHADER_MODEL_6_10 0x6a +#endif // D3D_SHADER_MODEL_6_10 + +static PalImageUsages imageUsageFromD3D12(D3D12_FORMAT_SUPPORT1 flags) +{ + PalImageUsages usages = 0; + if (flags & D3D12_FORMAT_SUPPORT1_RENDER_TARGET) { + usages |= PAL_IMAGE_USAGE_COLOR_ATTACHEMENT; + } + + if (flags & D3D12_FORMAT_SUPPORT1_DEPTH_STENCIL) { + usages |= PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT; + } + + if (flags & D3D12_FORMAT_SUPPORT1_SHADER_SAMPLE) { + usages |= PAL_IMAGE_USAGE_SAMPLED; + } + + usages |= PAL_IMAGE_USAGE_TRANSFER_DST; + usages |= PAL_IMAGE_USAGE_TRANSFER_SRC; + return usages; +} + +PalResult PAL_CALL enumerateAdaptersD3D12( + uint32_t* count, + PalAdapter** outAdapters) +{ + uint32_t adapterCount = 0; + IDXGIAdapter* adapter = nullptr; + IDXGIAdapter4* dxAdapters[32]; + ID3D12Device* devices[32]; + D3D_FEATURE_LEVEL deviceLevels[32]; + + D3D_FEATURE_LEVEL levels[] = { + D3D_FEATURE_LEVEL_12_2, + D3D_FEATURE_LEVEL_12_1, + D3D_FEATURE_LEVEL_12_0, + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0}; + + if (s_D3D12.adapters) { + palFree(s_D3D12.allocator, s_D3D12.adapters); + } + + while ( + SUCCEEDED(s_D3D12.factory->lpVtbl->EnumAdapters(s_D3D12.factory, adapterCount, &adapter))) { + if (outAdapters) { + IDXGIAdapter4* tmp = nullptr; + if (SUCCEEDED(adapter->lpVtbl->QueryInterface(adapter, &IID_Adapter, (void**)&tmp))) { + dxAdapters[adapterCount] = tmp; + } + + // create a temp device for every adapter to use as an instance to check features. + ID3D12Device* device = nullptr; + for (int i = 0; i < 5; i++) { + HRESULT result = + s_D3D12.createDevice((IUnknown*)tmp, levels[i], &IID_Device, (void**)&device); + + if (SUCCEEDED(result)) { + deviceLevels[adapterCount] = levels[i]; + devices[adapterCount] = device; + break; + } + } + } + adapter->lpVtbl->Release(adapter); + adapterCount++; + } + + if (outAdapters) { + s_D3D12.adapters = palAllocate(s_D3D12.allocator, sizeof(AdapterD3D12) * adapterCount, 0); + if (!s_D3D12.adapters) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // fill the array with AdapterD3D12 structs + for (int i = 0; i < *count; i++) { + AdapterD3D12* tmp = &s_D3D12.adapters[i]; + tmp->handle = dxAdapters[i]; + tmp->tmpDevice = devices[i]; + tmp->level = deviceLevels[i]; + outAdapters[i] = (PalAdapter*)tmp; + } + s_D3D12.adapterCount = *count; + + } else { + *count = adapterCount; + } + + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL getAdapterInfoD3D12( + PalAdapter* adapter, + PalAdapterInfo* info) +{ + HRESULT result = 0; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + DXGI_ADAPTER_DESC3 desc; + D3D12_FEATURE_DATA_ARCHITECTURE1 arch = {0}; + ID3D12Device* device = adapterImpl->tmpDevice; + + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {0}; + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + adapterImpl->handle->lpVtbl->GetDesc3(adapterImpl->handle, &desc); + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + + info->shaderFormats = PAL_SHADER_FORMAT_DXBC; + if (shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0 && result == S_OK) { + info->shaderFormats |= PAL_SHADER_FORMAT_DXIL; + } + + LARGE_INTEGER driverVersion = {0}; + info->driverVersion = 0; + result = adapterImpl->handle->lpVtbl->CheckInterfaceSupport( + adapterImpl->handle, + &IID_Adapter, + &driverVersion); + + if (SUCCEEDED(result)) { + info->driverVersion = (uint64_t)driverVersion.QuadPart; + } + + info->vendorId = desc.VendorId; + info->deviceId = desc.DeviceId; + info->apiType = PAL_ADAPTER_API_TYPE_D3D12; + info->sharedMemory = desc.SharedSystemMemory; + strcpy(info->backendName, "PAL"); + + WideCharToMultiByte( + CP_UTF8, + 0, + desc.Description, + -1, + info->name, + PAL_ADAPTER_NAME_SIZE, + nullptr, + nullptr); + + device->lpVtbl->CheckFeatureSupport(device, D3D12_FEATURE_ARCHITECTURE1, &arch, sizeof(arch)); + + if (arch.UMA == PAL_TRUE) { + info->type = PAL_ADAPTER_TYPE_INTEGRATED; + + } else { + info->type = PAL_ADAPTER_TYPE_DISCRETE; + } + + if (desc.Flags & DXGI_ADAPTER_FLAG3_SOFTWARE) { + info->type = PAL_ADAPTER_TYPE_CPU; + } + + if (desc.DedicatedVideoMemory > 0) { + info->vram = desc.DedicatedVideoMemory; + + } else if (desc.DedicatedVideoMemory == 0 && desc.DedicatedSystemMemory > 0) { + info->vram = desc.DedicatedSystemMemory; + } +} + +void PAL_CALL getAdapterCapabilitiesD3D12( + PalAdapter* adapter, + PalAdapterCapabilities* caps) +{ + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + PalViewportCapabilities* viewportCaps = &caps->viewportCaps; + PalImageCapabilities* imageCaps = &caps->imageCaps; + PalResourceCapabilities* resourceCaps = &caps->resourceCaps; + PalComputeCapabilities* computeCaps = &caps->computeCaps; + + caps->maxComputeQueues = 2; // safe default + caps->maxGraphicsQueues = 2; // safe default + caps->maxCopyQueues = 2; // safe default + + caps->maxColorAttachments = D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; + caps->maxUniformBufferSize = D3D12_REQ_IMMEDIATE_CONSTANT_BUFFER_ELEMENT_COUNT * 16; + caps->maxStorageBufferSize = 2147483648; // 2 GIB + caps->maxPushConstantSize = 256; + + caps->maxVertexLayouts = 32; // safe + caps->maxVertexAttributes = 32; // safe + caps->maxTessellationPatchPoint = 32; + + // viewport limits + viewportCaps->maxWidth = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + viewportCaps->maxHeight = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + viewportCaps->minBoundsRange = (float)D3D12_VIEWPORT_BOUNDS_MIN; + viewportCaps->maxBoundsRange = (float)D3D12_VIEWPORT_BOUNDS_MAX; + + // image limits + imageCaps->maxWidth = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + imageCaps->maxHeight = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + imageCaps->maxDepth = D3D12_REQ_TEXTURE3D_U_V_OR_W_DIMENSION; + imageCaps->maxArrayLayers = D3D12_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION; + + // d3d12 does not give this but we calculate from the max width and width + uint32_t a = imageCaps->maxWidth; + uint32_t b = imageCaps->maxHeight; + uint32_t c = imageCaps->maxDepth; + + uint32_t tmp = a > b ? a : b; + uint32_t size = tmp > c ? tmp : c; + uint32_t levels = 0; + while (size > 0) { + // divide by two + size = size / 2; + levels++; + } + imageCaps->maxMipLevels = levels; + + // resource limits + // always supported + getDescriptorTierLimitsD3D12(adapterImpl->tmpDevice, resourceCaps, nullptr); + resourceCaps->maxBoundSets = 32; + + // compute limits + computeCaps->maxWorkGroupInvocations = D3D12_CS_THREAD_GROUP_MAX_THREADS_PER_GROUP; + computeCaps->maxWorkGroupCount[0] = D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION; + computeCaps->maxWorkGroupCount[1] = D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION; + computeCaps->maxWorkGroupCount[2] = D3D12_CS_DISPATCH_MAX_THREAD_GROUPS_PER_DIMENSION; + computeCaps->maxWorkGroupSize[0] = D3D12_CS_THREAD_GROUP_MAX_X; + computeCaps->maxWorkGroupSize[1] = D3D12_CS_THREAD_GROUP_MAX_Y; + computeCaps->maxWorkGroupSize[2] = D3D12_CS_THREAD_GROUP_MAX_Z; +} + +PalAdapterFeatures PAL_CALL getAdapterFeaturesD3D12(PalAdapter* adapter) +{ + HRESULT result; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + PalAdapterFeatures features = 0; + ID3D12Device* device = adapterImpl->tmpDevice; + + D3D12_FEATURE_DATA_D3D12_OPTIONS options = {0}; + D3D12_FEATURE_DATA_D3D12_OPTIONS3 options3 = {0}; + D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = {0}; + D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = {0}; + D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = {0}; + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {0}; + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS, + &options, + sizeof(options)); + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS3, + &options3, + sizeof(options3)); + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS5, + &options5, + sizeof(options5)); + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS6, + &options6, + sizeof(options6)); + + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_D3D12_OPTIONS7, + &options7, + sizeof(options7)); + + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_5_1; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + + if (shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_5_1 && result == S_OK) { + features |= PAL_ADAPTER_FEATURE_TESSELLATION_SHADER; + features |= PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING; + features |= PAL_ADAPTER_FEATURE_SHADER_FLOAT64; + } + + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_2; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + + if (shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_2 && result == S_OK) { + features |= PAL_ADAPTER_FEATURE_SHADER_FLOAT16; + features |= PAL_ADAPTER_FEATURE_SHADER_INT16; + } + + shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_0; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + + if (shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_0 && result == S_OK) { + features |= PAL_ADAPTER_FEATURE_SHADER_INT64; + } + + if (options3.ViewInstancingTier != D3D12_VIEW_INSTANCING_TIER_NOT_SUPPORTED) { + features |= PAL_ADAPTER_FEATURE_MULTI_VIEW; + } + + if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED) { + features |= PAL_ADAPTER_FEATURE_RAY_TRACING; + features |= PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING; + } + + if (options5.RaytracingTier >= D3D12_RAYTRACING_TIER_1_1) { + features |= PAL_ADAPTER_FEATURE_RAY_QUERY; + } + + if (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED) { + features |= PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE; + features |= PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT; + } + + if (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED) { + features |= PAL_ADAPTER_FEATURE_MESH_SHADER; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT; + } + + if (!(options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_1)) { + features |= PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING; + } + + // this features are supported on d3d12 + features |= PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY; + features |= PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE; + features |= PAL_ADAPTER_FEATURE_MULTI_VIEWPORT; + features |= PAL_ADAPTER_FEATURE_GEOMETRY_SHADER; + features |= PAL_ADAPTER_FEATURE_SWAPCHAIN; + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + features |= PAL_ADAPTER_FEATURE_POLYGON_MODE_LINE; + features |= PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DISPATCH; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT; + features |= PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY; + features |= PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS; + + if (adapterImpl->level >= D3D_FEATURE_LEVEL_12_0) { + features |= PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE; + } + return features; +} + +uint32_t PAL_CALL getHighestSupportedShaderTargetD3D12( + PalAdapter* adapter, + PalShaderFormats shaderFormat) +{ + if (shaderFormat != PAL_SHADER_FORMAT_DXIL && shaderFormat != PAL_SHADER_FORMAT_DXBC) { + return 0; + } + + if (shaderFormat == PAL_SHADER_FORMAT_DXBC) { + return PAL_MAKE_SHADER_TARGET(5, 1); + } + + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = {0}; + + D3D_SHADER_MODEL models[11]; + models[0] = D3D_SHADER_MODEL_6_10; + models[1] = D3D_SHADER_MODEL_6_9; + models[2] = D3D_SHADER_MODEL_6_8; + models[3] = D3D_SHADER_MODEL_6_7; + models[4] = D3D_SHADER_MODEL_6_6; + models[5] = D3D_SHADER_MODEL_6_5; + models[6] = D3D_SHADER_MODEL_6_4; + models[7] = D3D_SHADER_MODEL_6_3; + models[8] = D3D_SHADER_MODEL_6_2; + models[9] = D3D_SHADER_MODEL_6_1; + models[10] = D3D_SHADER_MODEL_6_0; + + // find the highest supported shader model + HRESULT result = 0; + D3D_SHADER_MODEL highestModel = D3D_SHADER_MODEL_5_1; + for (int i = 0; i < 11; i++) { + shaderModel.HighestShaderModel = models[i]; + result = adapterImpl->tmpDevice->lpVtbl->CheckFeatureSupport( + adapterImpl->tmpDevice, + D3D12_FEATURE_SHADER_MODEL, + &shaderModel, + sizeof(shaderModel)); + + if (shaderModel.HighestShaderModel >= models[i] && result == S_OK) { + highestModel = models[i]; + break; + } + } + + if (highestModel == D3D_SHADER_MODEL_5_1) { + return 0; + } + + if (highestModel >= D3D_SHADER_MODEL_6_10) { + return PAL_MAKE_SHADER_TARGET(6, 10); + + } else if (highestModel >= D3D_SHADER_MODEL_6_9) { + return PAL_MAKE_SHADER_TARGET(6, 9); + + } else if (highestModel >= D3D_SHADER_MODEL_6_8) { + return PAL_MAKE_SHADER_TARGET(6, 8); + + } else if (highestModel >= D3D_SHADER_MODEL_6_6) { + return PAL_MAKE_SHADER_TARGET(6, 6); + + } else if (highestModel >= D3D_SHADER_MODEL_6_5) { + return PAL_MAKE_SHADER_TARGET(6, 5); + + } else if (highestModel >= D3D_SHADER_MODEL_6_4) { + return PAL_MAKE_SHADER_TARGET(6, 4); + + } else if (highestModel >= D3D_SHADER_MODEL_6_3) { + return PAL_MAKE_SHADER_TARGET(6, 3); + + } else if (highestModel >= D3D_SHADER_MODEL_6_2) { + return PAL_MAKE_SHADER_TARGET(6, 2); + + } else if (highestModel >= D3D_SHADER_MODEL_6_1) { + return PAL_MAKE_SHADER_TARGET(6, 1); + + } else if (highestModel >= D3D_SHADER_MODEL_6_0) { + return PAL_MAKE_SHADER_TARGET(6, 0); + } + + return 0; +} + +void PAL_CALL enumerateFormatsD3D12( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats) +{ + int32_t fmtCount = 0; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + ID3D12Device* device = adapterImpl->tmpDevice; + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {0}; + + for (int i = 0; i < PAL_FORMAT_COUNT; i++) { + DXGI_FORMAT fmt = formatToD3D12((PalFormat)i); + if (fmt == DXGI_FORMAT_UNKNOWN) { + continue; + } + + support.Format = fmt; + device->lpVtbl + ->CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)); + + if (support.Support1 == 0 && support.Support2 == 0) { + // format not supported + continue; + } + + if (outFormats) { + if (fmtCount < *count) { + PalFormatInfo* fmtInfo = &outFormats[fmtCount++]; + fmtInfo->format = (PalFormat)i; + fmtInfo->usages = imageUsageFromD3D12(support.Support1); + } + + } else { + fmtCount++; + } + } + if (!outFormats) { + *count = fmtCount; + } +} + +PalBool PAL_CALL isFormatSupportedD3D12( + PalAdapter* adapter, + PalFormat format) +{ + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + ID3D12Device* device = adapterImpl->tmpDevice; + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {0}; + + DXGI_FORMAT fmt = formatToD3D12(format); + if (fmt == DXGI_FORMAT_UNKNOWN) { + return PAL_FALSE; + } + + support.Format = fmt; + device->lpVtbl + ->CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_SUPPORT, &support, sizeof(support)); + + if (support.Support1 == 0 && support.Support2 == 0) { + return PAL_FALSE; + } + + return PAL_TRUE; +} + +PalImageUsages PAL_CALL queryFormatImageUsagesD3D12( + PalAdapter* adapter, + PalFormat format) +{ + HRESULT result; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + ID3D12Device* device = adapterImpl->tmpDevice; + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {0}; + + DXGI_FORMAT fmt = formatToD3D12(format); + if (fmt == DXGI_FORMAT_UNKNOWN) { + return 0; + } + + support.Format = fmt; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_FORMAT_SUPPORT, + &support, + sizeof(support)); + + if (FAILED(result)) { + return 0; + } + + if (support.Support1 == 0 && support.Support2 == 0) { + // format not supported + return 0; + } + + PalImageUsages usages = imageUsageFromD3D12(support.Support1); + if (support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_STORE || + support.Support2 & D3D12_FORMAT_SUPPORT2_UAV_TYPED_LOAD) { + usages |= PAL_IMAGE_USAGE_STORAGE; + } + + return usages; +} + +PalSampleCount PAL_CALL queryFormatSampleCountD3D12( + PalAdapter* adapter, + PalFormat format) +{ + HRESULT result; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + ID3D12Device* device = adapterImpl->tmpDevice; + D3D12_FEATURE_DATA_FORMAT_SUPPORT support = {0}; + + DXGI_FORMAT fmt = formatToD3D12(format); + if (fmt == DXGI_FORMAT_UNKNOWN) { + return PAL_FALSE; + } + + support.Format = fmt; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_FORMAT_SUPPORT, + &support, + sizeof(support)); + + if (FAILED(result)) { + return PAL_FALSE; + } + + if (support.Support1 == 0 && support.Support2 == 0) { + // format not supported + return PAL_FALSE; + } + + // check sample count + UINT sampleCounts[] = {64, 32, 16, 8, 4, 2}; + D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS samples = {0}; + samples.Format = fmt; + + uint32_t tmp = 0; + for (int i = 0; i < 6; i++) { + samples.SampleCount = sampleCounts[i]; + result = device->lpVtbl->CheckFeatureSupport( + device, + D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, + &samples, + sizeof(samples)); + + if (SUCCEEDED(result) && samples.NumQualityLevels > 0) { + tmp = samples.SampleCount; + break; + } + } + + if (tmp == 64) { + return PAL_SAMPLE_COUNT_64; + } else if (tmp == 32) { + return PAL_SAMPLE_COUNT_32; + } else if (tmp == 16) { + return PAL_SAMPLE_COUNT_16; + } else if (tmp == 8) { + return PAL_SAMPLE_COUNT_8; + } else if (tmp == 4) { + return PAL_SAMPLE_COUNT_4; + } else if (tmp == 2) { + return PAL_SAMPLE_COUNT_2; + } else { + return PAL_SAMPLE_COUNT_1; + } +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_as_d3d12.c b/src/graphics/d3d12/pal_as_d3d12.c new file mode 100644 index 00000000..8a9eb20d --- /dev/null +++ b/src/graphics/d3d12/pal_as_d3d12.c @@ -0,0 +1,82 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalResult PAL_CALL createAccelerationstructureD3D12( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs) +{ + HRESULT result; + AccelerationStructureD3D12* as = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + BufferD3D12* bufferImpl = (BufferD3D12*)info->buffer; + + as = palAllocate(s_D3D12.allocator, sizeof(AccelerationStructureD3D12), 0); + if (!as) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + as->type = info->type; + as->handle = bufferImpl->handle; + as->address = as->handle->lpVtbl->GetGPUVirtualAddress(as->handle); + as->address += info->offset; + *outAs = (PalAccelerationStructure*)as; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyAccelerationstructureD3D12(PalAccelerationStructure* as) +{ + AccelerationStructureD3D12* asImpl = (AccelerationStructureD3D12*)as; + palFree(s_D3D12.allocator, asImpl); +} + +void PAL_CALL getAccelerationStructureBuildSizeD3D12( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildInfo = {0}; + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO sizeInfo = {0}; + + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL) { + D3D12_RAYTRACING_GEOMETRY_DESC* geometries = nullptr; + geometries = + palAllocate(s_D3D12.allocator, sizeof(D3D12_RAYTRACING_GEOMETRY_DESC) * info->count, 0); + + if (!geometries) { + return; + } + + memset(geometries, 0, sizeof(D3D12_RAYTRACING_GEOMETRY_DESC) * info->count); + fillBuildInfoD3D12(PAL_TRUE, info, geometries, &buildInfo); + + deviceImpl->handle->lpVtbl->GetRaytracingAccelerationStructurePrebuildInfo( + deviceImpl->handle, + &buildInfo.Inputs, + &sizeInfo); + + palFree(s_D3D12.allocator, geometries); + + } else { + fillBuildInfoD3D12(PAL_TRUE, info, nullptr, &buildInfo); + + deviceImpl->handle->lpVtbl->GetRaytracingAccelerationStructurePrebuildInfo( + deviceImpl->handle, + &buildInfo.Inputs, + &sizeInfo); + } + + size->accelerationStructureSize = sizeInfo.ResultDataMaxSizeInBytes; + size->scratchBufferSize = sizeInfo.ScratchDataSizeInBytes; + size->updateScratchBufferSize = sizeInfo.UpdateScratchDataSizeInBytes; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_buffer_d3d12.c b/src/graphics/d3d12/pal_buffer_d3d12.c new file mode 100644 index 00000000..75ba9c87 --- /dev/null +++ b/src/graphics/d3d12/pal_buffer_d3d12.c @@ -0,0 +1,331 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +static uint32_t getSupportedMemoryTypes(PalBufferUsages usages) +{ + uint32_t masks = 0; + uint32_t gpuBit = (1u << PAL_MEMORY_TYPE_GPU_ONLY); + uint32_t uploadBit = (1u << PAL_MEMORY_TYPE_CPU_UPLOAD); + uint32_t readBackBit = (1u << PAL_MEMORY_TYPE_CPU_READBACK); + + masks |= gpuBit; + masks |= uploadBit; + masks |= readBackBit; + + if (usages & PAL_BUFFER_USAGE_STORAGE) { + masks &= ~uploadBit; + masks &= ~readBackBit; + } + + if (usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE) { + masks &= ~uploadBit; + masks &= ~readBackBit; + } + + if (usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH) { + masks &= ~uploadBit; + masks &= ~readBackBit; + } + + if (usages & PAL_BUFFER_USAGE_TRANSFER_DST) { + masks |= (1u << gpuBit); + masks &= ~uploadBit; + } + + if (usages & PAL_BUFFER_USAGE_TRANSFER_SRC) { + masks |= (1u << gpuBit); + masks &= ~readBackBit; + } + + return masks; +} + +PalResult PAL_CALL createBufferD3D12( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer) +{ + HRESULT result; + BufferD3D12* buffer = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + + buffer = palAllocate(s_D3D12.allocator, sizeof(BufferD3D12), 0); + if (!buffer) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(buffer, 0, sizeof(BufferD3D12)); + buffer->desc.Width = info->size; + buffer->desc.Height = 1; + buffer->desc.DepthOrArraySize = 1; + buffer->desc.MipLevels = 1; + buffer->desc.SampleDesc.Count = 1; + buffer->desc.SampleDesc.Quality = 0; + buffer->desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + buffer->desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + if (info->usages & PAL_BUFFER_USAGE_STORAGE) { + buffer->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + + if (info->usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE) { + buffer->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + + if (info->usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH) { + buffer->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + + buffer->isMemoryManaged = PAL_FALSE; + if (info->memoryUsage != PAL_BUFFER_MEMORY_USAGE_MANUAL) { + + D3D12_HEAP_PROPERTIES heapProps = {0}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + D3D12_RESOURCE_STATES state = D3D12_RESOURCE_STATE_COMMON; + buffer->canStateChange = PAL_TRUE; + + uint32_t masks = getSupportedMemoryTypes(info->usages); + if (info->memoryUsage == PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY) { + if (!(masks & (1u << PAL_MEMORY_TYPE_GPU_ONLY))) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (info->memoryUsage == PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD) { + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + state = D3D12_RESOURCE_STATE_GENERIC_READ; + buffer->canStateChange = PAL_FALSE; + + if (!(masks & (1u << PAL_MEMORY_TYPE_CPU_UPLOAD))) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (info->memoryUsage == PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK) { + heapProps.Type = D3D12_HEAP_TYPE_READBACK; + state = D3D12_RESOURCE_STATE_COPY_DEST; + buffer->canStateChange = PAL_FALSE; + + if (!(masks & (1u << PAL_MEMORY_TYPE_CPU_READBACK))) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &buffer->desc, + state, + nullptr, + &IID_Resource, + (void**)&buffer->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + buffer->isMemoryManaged = PAL_TRUE; + } + + buffer->device = deviceImpl; + buffer->usages = info->usages; + buffer->size = info->size; + *outBuffer = (PalBuffer*)buffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyBufferD3D12(PalBuffer* buffer) +{ + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + if (bufferImpl->isMemoryManaged) { + bufferImpl->handle->lpVtbl->Release(bufferImpl->handle); + } + palFree(s_D3D12.allocator, bufferImpl); +} + +void PAL_CALL getBufferMemoryRequirementsD3D12( + PalBuffer* buffer, + PalMemoryRequirements* requirements) +{ + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + ID3D12Device5* device = bufferImpl->device->handle; + + D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = {0}; + D3D12_RESOURCE_ALLOCATION_INFO __ret = {0}; + allocationInfo = + *device->lpVtbl->GetResourceAllocationInfo(device, &__ret, 0, 1, &bufferImpl->desc); + + requirements->supportedMemoryTypes = getSupportedMemoryTypes(bufferImpl->usages); + requirements->alignment = allocationInfo.Alignment; + requirements->size = allocationInfo.SizeInBytes; +} + +void PAL_CALL computeInstanceStagingSizeD3D12( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize) +{ + *outSize = sizeof(D3D12_RAYTRACING_INSTANCE_DESC) * instanceCount; +} + +void PAL_CALL computeImageStagingRequirementsD3D12( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements) +{ + uint32_t imageFormatSize = getFormatSizeD3D12(imageFormat); + uint32_t rowPitch = _ALIGN((uint64_t)copyInfo->imageWidth * imageFormatSize, TEXTURE_PITCH); + uint32_t bufferImageHeight = 0; + if (copyInfo->bufferImageHeight) { + bufferImageHeight = copyInfo->bufferImageHeight; + } else { + bufferImageHeight = copyInfo->imageHeight; + } + + requirements->bufferRowLength = rowPitch; + requirements->bufferImageHeight = bufferImageHeight; + requirements->bufferSize = (uint64_t)rowPitch * bufferImageHeight * copyInfo->imageDepth; +} + +void PAL_CALL writeInstanceStagingD3D12( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr) +{ + D3D12_RAYTRACING_INSTANCE_DESC* data = ptr; + for (int i = 0; i < instanceCount; i++) { + PalAccelerationStructureInstance* src = &instances[i]; + D3D12_RAYTRACING_INSTANCE_DESC* dst = &data[i]; + AccelerationStructureD3D12* as = (AccelerationStructureD3D12*)src->blas; + + dst->InstanceMask = src->mask; + dst->InstanceID = src->instanceId; + dst->AccelerationStructure = as->address; + dst->InstanceContributionToHitGroupIndex = src->hitGroupOffset; + dst->Flags = instanceFlagsToD3D12(src->flags); + memcpy(dst->Transform, src->transform, sizeof(float) * 12); + } +} + +void PAL_CALL writeImageStagingD3D12( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr) +{ + uint32_t imageFormatSize = getFormatSizeD3D12(imageFormat); + uint32_t srcRowPitch = copyInfo->imageWidth * imageFormatSize; + const uint32_t dstSlicePitch = copyInfo->bufferRowLength * copyInfo->bufferImageHeight; + const uint32_t srcSlicePitch = srcRowPitch * copyInfo->imageHeight; + + // manually offset the buffer with the provided offset + uint8_t* dst = (uint8_t*)ptr + copyInfo->bufferOffset; + const uint8_t* src = (const uint8_t*)srcData; + + // write to destination pointer + for (uint32_t z = 0; z < copyInfo->imageDepth; z++) { + for (uint32_t y = 0; y < copyInfo->imageHeight; y++) { + memcpy( + dst + z * dstSlicePitch + y * copyInfo->bufferRowLength, + src + z * srcSlicePitch + y * srcRowPitch, + srcRowPitch); + } + } +} + +PalResult PAL_CALL bindBufferMemoryD3D12( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset) +{ + HRESULT result; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + DeviceD3D12* device = bufferImpl->device; + MemoryD3D12* memoryImpl = (MemoryD3D12*)memory; + if (bufferImpl->isMemoryManaged) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + D3D12_RESOURCE_STATES state = 0; + bufferImpl->canStateChange = PAL_TRUE; + if (memoryImpl->type == PAL_MEMORY_TYPE_CPU_UPLOAD) { + state = D3D12_RESOURCE_STATE_GENERIC_READ; + bufferImpl->canStateChange = PAL_FALSE; + + } else if (memoryImpl->type == PAL_MEMORY_TYPE_CPU_UPLOAD) { + state = D3D12_RESOURCE_STATE_COPY_DEST; + bufferImpl->canStateChange = PAL_FALSE; + } + + if (bufferImpl->usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE) { + state = D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE; + bufferImpl->canStateChange = PAL_FALSE; + } + + if (bufferImpl->usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH) { + state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + } + + result = device->handle->lpVtbl->CreatePlacedResource( + device->handle, + memoryImpl->handle, + offset, + &bufferImpl->desc, + state, + nullptr, + &IID_Resource, + (void**)&bufferImpl->handle); + + if (FAILED(result)) { + pollMessagesD3D12(device); + return makeResultD3D12(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL mapBufferD3D12( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr) +{ + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + void* ptr = nullptr; + HRESULT result = bufferImpl->handle->lpVtbl->Map(bufferImpl->handle, 0, nullptr, &ptr); + if (FAILED(result)) { + return makeResultD3D12(result); + } + + *outPtr = (uint8_t*)ptr + offset; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL unmapBufferD3D12(PalBuffer* buffer) +{ + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + bufferImpl->handle->lpVtbl->Unmap(bufferImpl->handle, 0, nullptr); +} + +PalDeviceAddress PAL_CALL getBufferDeviceAddressD3D12(PalBuffer* buffer) +{ + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + if (bufferImpl->usages & PAL_BUFFER_USAGE_DEVICE_ADDRESS) { + return bufferImpl->handle->lpVtbl->GetGPUVirtualAddress(bufferImpl->handle); + } + return 0; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_command_pool_d3d12.c b/src/graphics/d3d12/pal_command_pool_d3d12.c new file mode 100644 index 00000000..8356b164 --- /dev/null +++ b/src/graphics/d3d12/pal_command_pool_d3d12.c @@ -0,0 +1,291 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalResult PAL_CALL createCommandPoolD3D12( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool) +{ + HRESULT result; + CommandPoolD3D12* pool = nullptr; + QueueD3D12* queueImpl = (QueueD3D12*)queue; + + pool = palAllocate(s_D3D12.allocator, sizeof(CommandPoolD3D12), 0); + if (!pool) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + switch (queueImpl->type) { + case PAL_QUEUE_TYPE_COMPUTE: { + pool->type = D3D12_COMMAND_LIST_TYPE_COMPUTE; + break; + } + + case PAL_QUEUE_TYPE_GRAPHICS: { + pool->type = D3D12_COMMAND_LIST_TYPE_DIRECT; + break; + } + + case PAL_QUEUE_TYPE_COPY: { + pool->type = D3D12_COMMAND_LIST_TYPE_COPY; + break; + } + } + + *outPool = (PalCommandPool*)pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyCommandPoolD3D12(PalCommandPool* pool) +{ + CommandPoolD3D12* cmdPool = (CommandPoolD3D12*)pool; + palFree(s_D3D12.allocator, cmdPool); +} + +PalResult PAL_CALL allocateCommandBufferD3D12( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + CommandBufferD3D12* cmdBuffer = nullptr; + CommandPoolD3D12* cmdPool = (CommandPoolD3D12*)pool; + + cmdBuffer = palAllocate(s_D3D12.allocator, sizeof(CommandBufferD3D12), 0); + if (!cmdBuffer) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(cmdBuffer, 0, sizeof(CommandBufferD3D12)); + cmdBuffer->primary = PAL_TRUE; + + // allocate memory for the linear allocator. We first start with 4KB + cmdBuffer->linearAllocator.memory = (uint8_t*)palAllocate(s_D3D12.allocator, 4096, 0); + if (!cmdBuffer->linearAllocator.memory) { + PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + cmdBuffer->linearAllocator.size = 4096; + cmdBuffer->linearAllocator.offset = 0; + D3D12_COMMAND_LIST_TYPE cmdBufferType = cmdPool->type; + if (type == PAL_COMMAND_BUFFER_TYPE_SECONDARY) { + cmdBufferType = D3D12_COMMAND_LIST_TYPE_BUNDLE; + cmdBuffer->primary = PAL_FALSE; + } + + // create an allocator + result = deviceImpl->handle->lpVtbl->CreateCommandAllocator( + deviceImpl->handle, + cmdBufferType, + &IID_CommandAllocator, + (void**)&cmdBuffer->allocator); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // create the command list + ID3D12GraphicsCommandList* cmdList = nullptr; + result = deviceImpl->handle->lpVtbl->CreateCommandList( + deviceImpl->handle, + 0, + cmdBufferType, + cmdBuffer->allocator, + nullptr, + &IID_CommandList, + (void**)&cmdList); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // create a tmp gpu buffer + D3D12_HEAP_PROPERTIES heapProps = {0}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + heapProps.VisibleNodeMask = 1; + heapProps.CreationNodeMask = 1; + + D3D12_RESOURCE_DESC bufferDesc = {0}; + bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + bufferDesc.Width = sizeof(D3D12_DISPATCH_RAYS_DESC); + bufferDesc.Height = 1; + bufferDesc.DepthOrArraySize = 1; + bufferDesc.MipLevels = 1; + bufferDesc.SampleDesc.Count = 1; + bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &bufferDesc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + &IID_Resource, + (void**)&cmdBuffer->buffer); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // create staging buffer + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &bufferDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + &IID_Resource, + (void**)&cmdBuffer->stagingBuffer); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + result = + cmdList->lpVtbl->QueryInterface(cmdList, &IID_CommandList6, (void**)&cmdBuffer->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + cmdList->lpVtbl->Release(cmdList); + cmdBuffer->handle->lpVtbl->Close(cmdBuffer->handle); + + cmdBuffer->device = deviceImpl; + *outCmdBuffer = (PalCommandBuffer*)cmdBuffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL freeCommandBufferD3D12(PalCommandBuffer* cmdBuffer) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl->Release(cmdBufferImpl->handle); + cmdBufferImpl->allocator->lpVtbl->Release(cmdBufferImpl->allocator); + cmdBufferImpl->buffer->lpVtbl->Release(cmdBufferImpl->buffer); + cmdBufferImpl->stagingBuffer->lpVtbl->Release(cmdBufferImpl->stagingBuffer); + + palFree(s_D3D12.allocator, (void*)cmdBufferImpl->linearAllocator.memory); + palFree(s_D3D12.allocator, cmdBufferImpl); +} + +PalResult PAL_CALL resetCommandBufferD3D12(PalCommandBuffer* cmdBuffer) +{ + HRESULT result; + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + result = cmdBufferImpl->allocator->lpVtbl->Reset(cmdBufferImpl->allocator); + if (FAILED(result)) { + return makeResultD3D12(result); + } + + result = cmdBufferImpl->handle->lpVtbl->Reset( + cmdBufferImpl->handle, + cmdBufferImpl->allocator, + nullptr); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + result = cmdBufferImpl->handle->lpVtbl->Close(cmdBufferImpl->handle); + if (FAILED(result)) { + pollMessagesD3D12(cmdBufferImpl->device); + return makeResultD3D12(result); + } + + cmdBufferImpl->pipeline = nullptr; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL submitCommandBufferD3D12( + PalQueue* queue, + PalCommandBufferSubmitInfo* info) +{ + HRESULT ret; + QueueD3D12* queueImpl = (QueueD3D12*)queue; + ID3D12CommandQueue* queueHandle = queueImpl->handle; + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)info->cmdBuffer; + + // poll pending messages + pollMessagesD3D12(cmdBufferImpl->device); + + // wait semaphore + if (info->waitSemaphore) { + SemaphoreD3D12* semaphore = (SemaphoreD3D12*)info->waitSemaphore; + if (semaphore->isTimeline) { + ret = queueHandle->lpVtbl->Wait(queueHandle, semaphore->handle, info->waitValue); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + + } else { + ret = queueHandle->lpVtbl->Wait(queueHandle, semaphore->handle, semaphore->value); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + + ret = semaphore->handle->lpVtbl->Signal(semaphore->handle, 0); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + + semaphore->value = 0; + } + } + + ID3D12CommandList* cmdLists[1] = {(ID3D12CommandList*)cmdBufferImpl->handle}; + queueHandle->lpVtbl->ExecuteCommandLists(queueHandle, 1, cmdLists); + pollMessagesD3D12(cmdBufferImpl->device); + + queueImpl->fenceValue++; + ret = queueHandle->lpVtbl->Signal(queueHandle, queueImpl->fence, queueImpl->fenceValue); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + + if (info->fence) { + FenceD3D12* fence = (FenceD3D12*)info->fence; + fence->value++; + ret = queueHandle->lpVtbl->Signal(queueHandle, fence->handle, fence->value); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + } + + if (info->signalSemaphore) { + SemaphoreD3D12* semaphore = (SemaphoreD3D12*)info->signalSemaphore; + if (semaphore->isTimeline) { + ret = queueHandle->lpVtbl->Signal(queueHandle, semaphore->handle, info->signalValue); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + + } else { + semaphore->value = 1; + ret = queueHandle->lpVtbl->Signal(queueHandle, semaphore->handle, semaphore->value); + if (FAILED(ret)) { + return makeResultD3D12(ret); + } + } + } + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_commands_d3d12.c b/src/graphics/d3d12/pal_commands_d3d12.c new file mode 100644 index 00000000..cc864568 --- /dev/null +++ b/src/graphics/d3d12/pal_commands_d3d12.c @@ -0,0 +1,1283 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +static D3D12_RENDER_PASS_FLAGS renderingFlagToD3D12(PalRenderingFlags flags) +{ + D3D12_RENDER_PASS_FLAGS renderingFlags = 0; + if (flags == PAL_RENDERING_FLAG_NONE) { + renderingFlags = D3D12_RENDER_PASS_FLAG_NONE; + } + + if (flags & PAL_RENDERING_FLAG_RESUMING) { + renderingFlags |= D3D12_RENDER_PASS_FLAG_RESUMING_PASS; + } + + if (flags & PAL_RENDERING_FLAG_SUSPENDING) { + renderingFlags |= D3D12_RENDER_PASS_FLAG_SUSPENDING_PASS; + } + + return renderingFlags; +} + +static D3D12_RESOURCE_STATES barrierToD3D12(PalUsageState state) +{ + switch (state) { + case PAL_USAGE_STATE_UNDEFINED: { + return D3D12_RESOURCE_STATE_COMMON; + } + + case PAL_USAGE_STATE_PRESENT: { + return D3D12_RESOURCE_STATE_PRESENT; + } + + case PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE: { + return D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + case PAL_USAGE_STATE_DEPTH_ATTACHMENT_READ: + case PAL_USAGE_STATE_STENCIL_ATTACHMENT_READ: { + return D3D12_RESOURCE_STATE_DEPTH_READ; + } + + case PAL_USAGE_STATE_DEPTH_ATTACHMENT_WRITE: + case PAL_USAGE_STATE_STENCIL_ATTACHMENT_WRITE: { + return D3D12_RESOURCE_STATE_DEPTH_WRITE; + } + + case PAL_USAGE_STATE_FRAGMENT_SHADING_RATE_ATTACHMENT_READ: { + return D3D12_RESOURCE_STATE_SHADING_RATE_SOURCE; + } + + case PAL_USAGE_STATE_TRANSFER_READ: { + return D3D12_RESOURCE_STATE_COPY_SOURCE; + } + + case PAL_USAGE_STATE_TRANSFER_WRITE: + case PAL_USAGE_STATE_HOST_READ: { + return D3D12_RESOURCE_STATE_COPY_DEST; + } + + case PAL_USAGE_STATE_VERTEX_READ: { + return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; + } + + case PAL_USAGE_STATE_INDEX_READ: { + return D3D12_RESOURCE_STATE_INDEX_BUFFER; + } + + case PAL_USAGE_STATE_INDIRECT_READ: { + return D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT; + } + + case PAL_USAGE_STATE_UNIFORM_READ: { + return D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER; + } + + case PAL_USAGE_STATE_SHADER_READ: { + return D3D12_RESOURCE_STATE_ALL_SHADER_RESOURCE; + } + + case PAL_USAGE_STATE_STORAGE_READ: + case PAL_USAGE_STATE_SHADER_WRITE: + case PAL_USAGE_STATE_STORAGE_WRITE: { + return D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + } + + case PAL_USAGE_STATE_HOST_WRITE: { + return D3D12_RESOURCE_STATE_GENERIC_READ; + } + + case PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ: + case PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE: { + return D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE; + } + } + + return D3D12_RESOURCE_STATE_COMMON; +} + +static D3D12_RESOLVE_MODE resolveModeToD3D12(PalResolveMode mode) +{ + switch (mode) { + case PAL_RESOLVE_MODE_AVERAGE: + return D3D12_RESOLVE_MODE_AVERAGE; + + case PAL_RESOLVE_MODE_MIN: + return D3D12_RESOLVE_MODE_MIN; + + case PAL_RESOLVE_MODE_MAX: + return D3D12_RESOLVE_MODE_MAX; + } + + return D3D12_RESOLVE_MODE_DECOMPRESS; +} + +static void commitShaderbindingTableUpdateD3D12( + CommandBufferD3D12* cmdBuffer, + ShaderBindingTableD3D12* sbt) +{ + if (!sbt->isDirty) { + return; + } + + // begin upload buffer copy to gpu buffer + cmdBuffer->handle->lpVtbl->CopyBufferRegion( + cmdBuffer->handle, + sbt->buffer, + 0, + sbt->stagingBuffer, + 0, + sbt->stagingBufferSize); + + // put a memory barrier + D3D12_RESOURCE_BARRIER barrier = {0}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.pResource = sbt->buffer; + + cmdBuffer->handle->lpVtbl->ResourceBarrier(cmdBuffer->handle, 1, &barrier); + sbt->isDirty = PAL_FALSE; +} + +PalResult PAL_CALL cmdBeginD3D12( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info) +{ + HRESULT result; + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + result = cmdBufferImpl->allocator->lpVtbl->Reset(cmdBufferImpl->allocator); + if (FAILED(result)) { + pollMessagesD3D12(cmdBufferImpl->device); + return makeResultD3D12(result); + } + + result = cmdBufferImpl->handle->lpVtbl->Reset( + cmdBufferImpl->handle, + cmdBufferImpl->allocator, + nullptr); + + if (FAILED(result)) { + pollMessagesD3D12(cmdBufferImpl->device); + return makeResultD3D12(result); + } + + cmdBufferImpl->linearAllocator.offset = 0; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL cmdEndD3D12(PalCommandBuffer* cmdBuffer) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + HRESULT result = cmdBufferImpl->handle->lpVtbl->Close(cmdBufferImpl->handle); + if (FAILED(result)) { + pollMessagesD3D12(cmdBufferImpl->device); + return makeResultD3D12(result); + } + + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL cmdExecuteCommandBufferD3D12( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer) +{ + CommandBufferD3D12* primaryCmdBufferImpl = (CommandBufferD3D12*)primaryCmdBuffer; + CommandBufferD3D12* secondaryCmdBufferImpl = (CommandBufferD3D12*)secondaryCmdBuffer; + + primaryCmdBufferImpl->handle->lpVtbl->ExecuteBundle( + primaryCmdBufferImpl->handle, + (ID3D12GraphicsCommandList*)secondaryCmdBufferImpl->handle); +} + +void PAL_CALL cmdSetFragmentShadingRateD3D12( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_SHADING_RATE shadingRate = shadingRateToD3D12(state->rate); + D3D12_SHADING_RATE_COMBINER combinerOps[2]; + for (int i = 0; i < 2; i++) { + combinerOps[i] = combinerOpsToD3D12(state->combinerOps[i]); + } + + cmdBufferImpl->handle->lpVtbl->RSSetShadingRate( + cmdBufferImpl->handle, + shadingRate, + combinerOps); +} + +void PAL_CALL cmdDrawMeshTasksD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl + ->DispatchMesh(cmdBufferImpl->handle, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL cmdDrawMeshTasksIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->meshSignature, + drawCount, + bufferImpl->handle, + 0, + nullptr, + 0); +} + +void PAL_CALL cmdDrawMeshTasksIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + BufferD3D12* countBufferImpl = (BufferD3D12*)countBuffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->meshSignature, + maxDrawCount, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0); +} + +void PAL_CALL cmdBuildAccelerationStructureD3D12( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildInfo = {0}; + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL) { + D3D12_RAYTRACING_GEOMETRY_DESC* geometries = nullptr; + geometries = palLinearAlloc( + &cmdBufferImpl->linearAllocator, + sizeof(D3D12_RAYTRACING_GEOMETRY_DESC) * info->count, + 0); + + memset(geometries, 0, sizeof(D3D12_RAYTRACING_GEOMETRY_DESC) * info->count); + fillBuildInfoD3D12(PAL_FALSE, info, geometries, &buildInfo); + cmdBufferImpl->handle->lpVtbl + ->BuildRaytracingAccelerationStructure(cmdBufferImpl->handle, &buildInfo, 0, nullptr); + + } else { + fillBuildInfoD3D12(PAL_FALSE, info, nullptr, &buildInfo); + cmdBufferImpl->handle->lpVtbl + ->BuildRaytracingAccelerationStructure(cmdBufferImpl->handle, &buildInfo, 0, nullptr); + } +} + +void PAL_CALL cmdBeginRenderingD3D12( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_RENDER_PASS_RENDER_TARGET_DESC colorAttachments[MAX_ATTACHMENTS]; + D3D12_RENDER_PASS_DEPTH_STENCIL_DESC depthStencilAttachment = {0}; + D3D12_CPU_DESCRIPTOR_HANDLE colorCpuHandles[MAX_ATTACHMENTS]; + D3D12_CPU_DESCRIPTOR_HANDLE depthStencilCpuHandle; + + PalAttachmentDesc* desc = nullptr; + ImageViewD3D12* imageView = nullptr; + ImageViewD3D12* resolveImageView = nullptr; + memset(colorAttachments, 0, sizeof(D3D12_RENDER_PASS_RENDER_TARGET_DESC) * MAX_ATTACHMENTS); + + for (int i = 0; i < info->colorAttachentCount; i++) { + D3D12_RENDER_PASS_RENDER_TARGET_DESC* attachment = &colorAttachments[i]; + desc = &info->colorAttachments[i]; + imageView = (ImageViewD3D12*)desc->imageView; + resolveImageView = (ImageViewD3D12*)desc->resolveImageView; + + RTVHeapAllocator* allocator = &imageView->device->rtvAllocator; + uint32_t size = allocator->incrementSize; + uint64_t base = allocator->baseOffset; + colorCpuHandles[i].ptr = getDescriptorHandleD3D12(imageView->heapIndex, size, base); + + attachment->BeginningAccess.Clear.ClearValue.Color[0] = desc->clearValue.color[0]; + attachment->BeginningAccess.Clear.ClearValue.Color[1] = desc->clearValue.color[1]; + attachment->BeginningAccess.Clear.ClearValue.Color[2] = desc->clearValue.color[2]; + attachment->BeginningAccess.Clear.ClearValue.Color[3] = desc->clearValue.color[3]; + + // load op + if (desc->loadOp == PAL_LOAD_OP_CLEAR) { + attachment->BeginningAccess.Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; + + } else if (desc->loadOp == PAL_LOAD_OP_LOAD) { + attachment->BeginningAccess.Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; + + } else if (desc->loadOp == PAL_LOAD_OP_DONT_CARE) { + attachment->BeginningAccess.Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD; + } + + // store op + if (desc->storeOp == PAL_STORE_OP_STORE) { + attachment->EndingAccess.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; + + } else if (desc->storeOp == PAL_STORE_OP_DONT_CARE) { + attachment->EndingAccess.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; + } + + attachment->cpuDescriptor = colorCpuHandles[i]; + if (resolveImageView) { + attachment->EndingAccess.Resolve.pDstResource = resolveImageView->image->handle; + attachment->EndingAccess.Resolve.Format = formatToD3D12(resolveImageView->format); + attachment->EndingAccess.Resolve.ResolveMode = resolveModeToD3D12(desc->resolveMode); + attachment->EndingAccess.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE; + } + } + + // depth attachment + if (info->depthStencilAttachment) { + desc = info->depthStencilAttachment; + imageView = (ImageViewD3D12*)desc->imageView; + resolveImageView = (ImageViewD3D12*)desc->resolveImageView; + + DSVHeapAllocator* allocator = &imageView->device->dsvAllocator; + uint32_t size = allocator->incrementSize; + uint64_t base = allocator->baseOffset; + depthStencilCpuHandle.ptr = getDescriptorHandleD3D12(imageView->heapIndex, size, base); + + D3D12_RENDER_PASS_BEGINNING_ACCESS* depthBegin = nullptr; + D3D12_RENDER_PASS_BEGINNING_ACCESS* stencilBegin = nullptr; + D3D12_RENDER_PASS_ENDING_ACCESS* depthEnd = nullptr; + D3D12_RENDER_PASS_ENDING_ACCESS* stencilEnd = nullptr; + + depthBegin = &depthStencilAttachment.DepthBeginningAccess; + stencilBegin = &depthStencilAttachment.StencilBeginningAccess; + depthEnd = &depthStencilAttachment.DepthEndingAccess; + stencilEnd = &depthStencilAttachment.StencilEndingAccess; + + depthBegin->Clear.ClearValue.DepthStencil.Depth = desc->clearValue.depth; + depthBegin->Clear.ClearValue.DepthStencil.Stencil = desc->clearValue.stencil; + stencilBegin->Clear.ClearValue.DepthStencil.Depth = desc->clearValue.depth; + stencilBegin->Clear.ClearValue.DepthStencil.Stencil = desc->clearValue.stencil; + + // load op + if (desc->loadOp == PAL_LOAD_OP_CLEAR) { + depthBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; + + } else if (desc->loadOp == PAL_LOAD_OP_LOAD) { + depthBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; + + } else if (desc->loadOp == PAL_LOAD_OP_DONT_CARE) { + depthBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD; + } + + // store op + if (desc->storeOp == PAL_STORE_OP_STORE) { + depthEnd->Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; + + } else if (desc->storeOp == PAL_STORE_OP_DONT_CARE) { + depthEnd->Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; + } + + // stencil load op + if (desc->loadOp == PAL_LOAD_OP_CLEAR) { + stencilBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR; + + } else if (desc->loadOp == PAL_LOAD_OP_LOAD) { + stencilBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE; + + } else if (desc->loadOp == PAL_LOAD_OP_DONT_CARE) { + stencilBegin->Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD; + } + + // stencil store op + if (desc->stencilStoreOp == PAL_STORE_OP_STORE) { + stencilEnd->Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE; + + } else if (desc->stencilStoreOp == PAL_STORE_OP_DONT_CARE) { + stencilEnd->Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD; + } + + depthStencilAttachment.cpuDescriptor = depthStencilCpuHandle; + if (resolveImageView) { + depthEnd->Resolve.pDstResource = resolveImageView->image->handle; + depthEnd->Resolve.Format = formatToD3D12(resolveImageView->format); + depthEnd->Resolve.ResolveMode = resolveModeToD3D12(desc->resolveMode); + depthEnd->Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_RESOLVE; + + stencilEnd->Resolve.pDstResource = depthEnd->Resolve.pDstResource; + stencilEnd->Resolve.Format = depthEnd->Resolve.Format; + stencilEnd->Resolve.ResolveMode = depthEnd->Resolve.ResolveMode; + stencilEnd->Type = depthEnd->Type; + } + } + + D3D12_CPU_DESCRIPTOR_HANDLE* tmpCpuHandle = nullptr; + D3D12_RENDER_PASS_DEPTH_STENCIL_DESC* tmpDesc = nullptr; + if (info->depthStencilAttachment) { + tmpCpuHandle = &depthStencilCpuHandle; + tmpDesc = &depthStencilAttachment; + } + + cmdBufferImpl->handle->lpVtbl->OMSetRenderTargets( + cmdBufferImpl->handle, + info->colorAttachentCount, + colorCpuHandles, + PAL_FALSE, + tmpCpuHandle); + + D3D12_RENDER_PASS_FLAGS flags = renderingFlagToD3D12(info->flags); + cmdBufferImpl->handle->lpVtbl->BeginRenderPass( + cmdBufferImpl->handle, + info->colorAttachentCount, + colorAttachments, + tmpDesc, + flags); +} + +void PAL_CALL cmdEndRenderingD3D12(PalCommandBuffer* cmdBuffer) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl->EndRenderPass(cmdBufferImpl->handle); +} + +void PAL_CALL cmdCopyBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + BufferD3D12* dstbuffer = (BufferD3D12*)dst; + BufferD3D12* srcBuffer = (BufferD3D12*)src; + cmdBufferImpl->handle->lpVtbl->CopyBufferRegion( + cmdBufferImpl->handle, + dstbuffer->handle, + copyInfo->dstOffset, + srcBuffer->handle, + copyInfo->srcOffset, + copyInfo->size); +} + +void PAL_CALL cmdCopyBufferToImageD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + ImageD3D12* dst = (ImageD3D12*)dstImage; + BufferD3D12* src = (BufferD3D12*)srcBuffer; + + D3D12_TEXTURE_COPY_LOCATION dstLocation = {0}; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLocation.pResource = dst->handle; + + D3D12_TEXTURE_COPY_LOCATION srcLocation = {0}; + srcLocation.pResource = src->handle; + srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT* footPrint = &srcLocation.PlacedFootprint; + + footPrint->Offset = copyInfo->bufferOffset; + footPrint->Footprint.Width = copyInfo->imageWidth; + footPrint->Footprint.Height = copyInfo->imageHeight; + footPrint->Footprint.Depth = copyInfo->imageDepth; + footPrint->Footprint.Format = formatToD3D12(dst->info.format); + + uint32_t imageFormatSize = getFormatSizeD3D12(dst->info.format); + uint64_t rowPitch = _ALIGN((uint64_t)copyInfo->imageWidth * imageFormatSize, TEXTURE_PITCH); + footPrint->Footprint.RowPitch = (UINT)rowPitch; + + uint32_t planeCount = 1; + if (copyInfo->imageAspect == PAL_IMAGE_ASPECT_DEPTH_STENCIL) { + planeCount = 2; + } + + D3D12_BOX box = {0}; + box.right = copyInfo->imageWidth; + box.bottom = copyInfo->imageHeight; + box.back = copyInfo->imageDepth; + + uint32_t level = copyInfo->ImageMipLevel; + uint32_t startLayer = copyInfo->ImageStartArrayLayer; + uint32_t layerCount = copyInfo->ImageArrayLayerCount; + uint32_t maxLayers = dst->info.arrayLayerCount; + uint32_t maxLevels = dst->info.mipLevelCount; + + for (uint32_t plane = 0; plane < planeCount; plane++) { + for (uint32_t layer = startLayer; layer < startLayer + layerCount; layer++) { + uint32_t index = level + (layer * maxLevels) + (plane * maxLevels * maxLayers); + + dstLocation.SubresourceIndex = index; + cmdBufferImpl->handle->lpVtbl->CopyTextureRegion( + cmdBufferImpl->handle, + &dstLocation, + copyInfo->imageOffsetX, + copyInfo->imageOffsetY, + copyInfo->imageOffsetZ, + &srcLocation, + &box); + } + } +} + +void PAL_CALL cmdCopyImageD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + ImageD3D12* dstImage = (ImageD3D12*)dst; + ImageD3D12* srcImage = (ImageD3D12*)src; + + D3D12_TEXTURE_COPY_LOCATION dstLocation = {0}; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLocation.pResource = dstImage->handle; + + D3D12_TEXTURE_COPY_LOCATION srcLocation = {0}; + srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLocation.pResource = srcImage->handle; + + D3D12_BOX box = {0}; + box.left = copyInfo->srcOffsetX; + box.top = copyInfo->srcOffsetY; + box.front = copyInfo->srcOffsetZ; + box.right = copyInfo->srcOffsetX + copyInfo->width; + box.bottom = copyInfo->srcOffsetY + copyInfo->height; + box.back = copyInfo->srcOffsetZ + copyInfo->depth; + + uint32_t planeCount = 1; + uint32_t layerCount = copyInfo->arrayLayerCount; + if (copyInfo->aspect == PAL_IMAGE_ASPECT_DEPTH_STENCIL) { + planeCount = 2; + } + + uint32_t dstLevel = copyInfo->dstMipLevel; + uint32_t dstStartLayer = copyInfo->dstStartArrayLayer; + uint32_t dstMaxLayers = dstImage->info.arrayLayerCount; + uint32_t dstMaxLevels = dstImage->info.mipLevelCount; + + uint32_t srcLevel = copyInfo->srcMipLevel; + uint32_t srcStartLayer = copyInfo->srcStartArrayLayer; + uint32_t srcMaxLayers = srcImage->info.arrayLayerCount; + uint32_t srcMaxLevels = srcImage->info.mipLevelCount; + + for (uint32_t plane = 0; plane < planeCount; plane++) { + for (uint32_t layer = 0; layer + layerCount; layer++) { + // clang-format off + uint32_t dstIndex = dstLevel + (dstStartLayer + layer * dstMaxLevels) + (plane * dstMaxLevels * dstMaxLayers); + uint32_t srcIndex = srcLevel + (srcStartLayer + layer * srcMaxLevels) + (plane * srcMaxLevels * srcMaxLayers); + // clang-format on + + dstLocation.SubresourceIndex = dstIndex; + srcLocation.SubresourceIndex = srcIndex; + + cmdBufferImpl->handle->lpVtbl->CopyTextureRegion( + cmdBufferImpl->handle, + &dstLocation, + copyInfo->dstOffsetX, + copyInfo->dstOffsetY, + copyInfo->dstOffsetZ, + &srcLocation, + &box); + } + } +} + +void PAL_CALL cmdCopyImageToBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + BufferD3D12* dst = (BufferD3D12*)dstBuffer; + ImageD3D12* src = (ImageD3D12*)srcImage; + + D3D12_TEXTURE_COPY_LOCATION dstLocation = {0}; + dstLocation.pResource = dst->handle; + dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT* footPrint = &dstLocation.PlacedFootprint; + footPrint->Offset = copyInfo->bufferOffset; + footPrint->Footprint.Width = copyInfo->imageWidth; + footPrint->Footprint.Height = copyInfo->imageHeight; + footPrint->Footprint.Depth = copyInfo->imageDepth; + footPrint->Footprint.Format = formatToD3D12(src->info.format); + + D3D12_TEXTURE_COPY_LOCATION srcLocation = {0}; + srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLocation.pResource = src->handle; + + uint32_t imageFormatSize = getFormatSizeD3D12(src->info.format); + uint64_t rowPitch = _ALIGN((uint64_t)copyInfo->imageWidth * imageFormatSize, TEXTURE_PITCH); + footPrint->Footprint.RowPitch = (UINT)rowPitch; + + D3D12_BOX box = {0}; + box.left = copyInfo->imageOffsetX; + box.top = copyInfo->imageOffsetY; + box.front = copyInfo->imageOffsetZ; + box.right = copyInfo->imageOffsetX + copyInfo->imageWidth; + box.bottom = copyInfo->imageOffsetY + copyInfo->imageHeight; + box.back = copyInfo->imageOffsetX + copyInfo->imageDepth; + + uint32_t planeCount = 1; + if (copyInfo->imageAspect == PAL_IMAGE_ASPECT_DEPTH_STENCIL) { + planeCount = 2; + } + + uint32_t level = copyInfo->ImageMipLevel; + uint32_t startLayer = copyInfo->ImageStartArrayLayer; + uint32_t layerCount = copyInfo->ImageArrayLayerCount; + uint32_t maxLayers = src->info.arrayLayerCount; + uint32_t maxLevels = src->info.mipLevelCount; + + for (uint32_t plane = 0; plane < planeCount; plane++) { + for (uint32_t layer = startLayer; layer < startLayer + layerCount; layer++) { + uint32_t index = level + (layer * maxLevels) + (plane * maxLevels * maxLayers); + + srcLocation.SubresourceIndex = index; + cmdBufferImpl->handle->lpVtbl->CopyTextureRegion( + cmdBufferImpl->handle, + &dstLocation, + 0, + 0, + 0, + &srcLocation, + &box); + } + } +} + +void PAL_CALL cmdBindPipelineD3D12( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + PipelineD3D12* pipelineImpl = (PipelineD3D12*)pipeline; + if (pipelineImpl->type == RAY_TRACING_PIPELINE) { + cmdBufferImpl->handle->lpVtbl->SetPipelineState1( + cmdBufferImpl->handle, + pipelineImpl->handle); + + cmdBufferImpl->handle->lpVtbl->SetComputeRootSignature( + cmdBufferImpl->handle, + pipelineImpl->layout->handle); + + } else { + cmdBufferImpl->handle->lpVtbl->SetPipelineState( + cmdBufferImpl->handle, + pipelineImpl->handle); + + cmdBufferImpl->handle->lpVtbl->SetComputeRootSignature( + cmdBufferImpl->handle, + pipelineImpl->layout->handle); + + if (pipelineImpl->type == GRAPHICS_PIPELINE) { + cmdBufferImpl->handle->lpVtbl->IASetPrimitiveTopology( + cmdBufferImpl->handle, + pipelineImpl->topology); + + cmdBufferImpl->handle->lpVtbl->SetGraphicsRootSignature( + cmdBufferImpl->handle, + pipelineImpl->layout->handle); + + if (pipelineImpl->hasFsr) { + cmdBufferImpl->handle->lpVtbl->RSSetShadingRate( + cmdBufferImpl->handle, + pipelineImpl->shadingRate, + pipelineImpl->combinerOps); + } + } + } + + cmdBufferImpl->pipeline = pipelineImpl; +} + +void PAL_CALL cmdSetViewportD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_VIEWPORT viewportsImpl[D3D12_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + for (int i = 0; i < count; i++) { + D3D12_VIEWPORT* tmp = &viewportsImpl[i]; + tmp->TopLeftX = viewports[i].x; + tmp->TopLeftY = viewports[i].y; + tmp->Width = viewports[i].width; + tmp->Height = viewports[i].height; + tmp->MinDepth = viewports[i].minDepth; + tmp->MaxDepth = viewports[i].maxDepth; + } + + cmdBufferImpl->handle->lpVtbl->RSSetViewports(cmdBufferImpl->handle, count, viewportsImpl); +} + +void PAL_CALL cmdSetScissorsD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_RECT scissorsImpl[D3D12_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + for (int i = 0; i < count; i++) { + D3D12_RECT* tmp = &scissorsImpl[i]; + tmp->left = scissors[i].x; + tmp->top = scissors[i].y; + tmp->right = scissors[i].width; + tmp->bottom = scissors[i].height; + } + + cmdBufferImpl->handle->lpVtbl->RSSetScissorRects(cmdBufferImpl->handle, count, scissorsImpl); +} + +void PAL_CALL cmdBindVertexBuffersD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + PipelineD3D12* pipeline = cmdBufferImpl->pipeline; + D3D12_VERTEX_BUFFER_VIEW* views = nullptr; + views = palLinearAlloc( + &cmdBufferImpl->linearAllocator, + sizeof(D3D12_VERTEX_BUFFER_VIEW) * count, + 0); + + for (int i = 0; i < count; i++) { + BufferD3D12* tmp = (BufferD3D12*)buffers[i]; + views[i].BufferLocation = tmp->handle->lpVtbl->GetGPUVirtualAddress(tmp->handle); + views[i].BufferLocation = views[i].BufferLocation + offsets[i]; + views[i].SizeInBytes = (UINT)tmp->size; + views[i].StrideInBytes = pipeline->strides[i]; + } + + cmdBufferImpl->handle->lpVtbl + ->IASetVertexBuffers(cmdBufferImpl->handle, firstSlot, count, views); +} + +void PAL_CALL cmdBindIndexBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + D3D12_INDEX_BUFFER_VIEW view = {0}; + BufferD3D12* indexBuffer = (BufferD3D12*)buffer; + + view.BufferLocation = indexBuffer->handle->lpVtbl->GetGPUVirtualAddress(indexBuffer->handle); + view.BufferLocation = view.BufferLocation + offset; + view.SizeInBytes = (UINT)indexBuffer->size; + if (type == PAL_INDEX_TYPE_UINT16) { + view.Format = DXGI_FORMAT_R16_UINT; + } else { + view.Format = DXGI_FORMAT_R32_UINT; + } + + cmdBufferImpl->handle->lpVtbl->IASetIndexBuffer(cmdBufferImpl->handle, &view); +} + +void PAL_CALL cmdDrawD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl->DrawInstanced( + cmdBufferImpl->handle, + vertexCount, + instanceCount, + firstVertex, + firstInstance); +} + +void PAL_CALL cmdDrawIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->drawSignature, + count, + bufferImpl->handle, + 0, + nullptr, + 0); +} + +void PAL_CALL cmdDrawIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + BufferD3D12* countBufferImpl = (BufferD3D12*)countBuffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->drawSignature, + maxDrawCount, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0); +} + +void PAL_CALL cmdDrawIndexedD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl->DrawIndexedInstanced( + cmdBufferImpl->handle, + indexCount, + instanceCount, + firstIndex, + vertexOffset, + firstInstance); +} + +void PAL_CALL cmdDrawIndexedIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->drawIndexedSignature, + count, + bufferImpl->handle, + 0, + nullptr, + 0); +} + +void PAL_CALL cmdDrawIndexedIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + BufferD3D12* countBufferImpl = (BufferD3D12*)countBuffer; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->drawIndexedSignature, + maxDrawCount, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0); +} + +void PAL_CALL cmdAccelerationStructureBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + AccelerationStructureD3D12* asImpl = (AccelerationStructureD3D12*)as; + D3D12_RESOURCE_BARRIER barrier = {0}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.UAV.pResource = asImpl->handle; + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); +} + +void PAL_CALL cmdImageBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + ImageD3D12* imageImpl = (ImageD3D12*)image; + D3D12_RESOURCE_STATES old, new; + D3D12_RESOURCE_BARRIER barrier = {0}; + + old = barrierToD3D12(info->oldState); + new = barrierToD3D12(info->newState); + + // read/write barrier without transition + if (old == new && old == D3D12_RESOURCE_STATE_UNORDERED_ACCESS) { + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.UAV.pResource = imageImpl->handle; + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); + return; + } + + uint32_t planeCount = 1; // for color or depth + if (subresourceRange->aspect == PAL_IMAGE_ASPECT_DEPTH_STENCIL) { + planeCount = 2; + } + + D3D12_RESOURCE_BARRIER* barriers = nullptr; + uint32_t levelCount = subresourceRange->mipLevelCount; + uint32_t layerCount = subresourceRange->layerArrayCount; + + uint32_t startLevel = subresourceRange->startMipLevel; + uint32_t startLayer = subresourceRange->startArrayLayer; + uint32_t maxLevels = imageImpl->info.mipLevelCount; + uint32_t maxLayers = imageImpl->info.arrayLayerCount; + uint32_t barrierCount = layerCount * levelCount * planeCount; + + if (startLevel == 0 && levelCount == maxLevels && startLayer == 0 && layerCount == maxLayers) { + // full resource + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = imageImpl->handle; + barrier.Transition.StateBefore = old; + barrier.Transition.StateAfter = new; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); + return; + } + + if (layerCount == 1 && layerCount == 1 && planeCount == 1) { + // single plane + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = imageImpl->handle; + barrier.Transition.StateBefore = old; + barrier.Transition.StateAfter = new; + barrier.Transition.Subresource = startLevel + startLayer * maxLevels; + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); + return; + } + + barriers = palLinearAlloc( + &cmdBufferImpl->linearAllocator, + sizeof(D3D12_RESOURCE_BARRIER) * barrierCount, + 0); + + uint32_t count = 0; + for (uint32_t plane = 0; plane < planeCount; plane++) { + for (uint32_t layer = startLayer; layer < startLayer + layerCount; layer++) { + for (uint32_t level = startLevel; level < startLevel + levelCount; level++) { + uint32_t index = level + (layer * maxLevels) + (plane * maxLevels * maxLayers); + + D3D12_RESOURCE_BARRIER* tmp = &barriers[count++]; + tmp->Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + tmp->Transition.pResource = imageImpl->handle; + tmp->Transition.StateBefore = old; + tmp->Transition.StateAfter = new; + tmp->Transition.Subresource = index; + } + } + } + + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, barrierCount, barriers); +} + +void PAL_CALL cmdBufferBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + D3D12_RESOURCE_STATES old, new; + if (!bufferImpl->canStateChange) { + return; + } + + old = barrierToD3D12(info->oldState); + new = barrierToD3D12(info->newState); + + D3D12_RESOURCE_BARRIER barrier = {0}; + if (old == new && D3D12_RESOURCE_STATE_UNORDERED_ACCESS) { + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.UAV.pResource = bufferImpl->handle; + } else { + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = bufferImpl->handle; + barrier.Transition.StateBefore = old; + barrier.Transition.StateAfter = new; + } + + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); +} + +void PAL_CALL cmdDispatchD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + cmdBufferImpl->handle->lpVtbl + ->Dispatch(cmdBufferImpl->handle, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL cmdDispatchIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + DeviceD3D12* device = cmdBufferImpl->device; + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->dispatchSignature, + 1, // one dispatch + bufferImpl->handle, + 0, + nullptr, + 0); +} + +void PAL_CALL cmdTraceRaysD3D12( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + ShaderBindingTableD3D12* sbtImpl = (ShaderBindingTableD3D12*)sbt; + uint64_t stride = sbtImpl->raygen.region.StrideInBytes; + D3D12_GPU_VIRTUAL_ADDRESS_RANGE raygenAddress = {0}; + raygenAddress.SizeInBytes = sbtImpl->raygen.region.SizeInBytes; + raygenAddress.StartAddress = sbtImpl->baseAddress + raygenIndex * stride; + + // we need to make sure the SBT is up to date + commitShaderbindingTableUpdateD3D12(cmdBufferImpl, sbtImpl); + + D3D12_DISPATCH_RAYS_DESC desc = {0}; + desc.Width = width; + desc.Height = height; + desc.Depth = depth; + desc.RayGenerationShaderRecord = raygenAddress; + desc.HitGroupTable = sbtImpl->hit.region; + desc.MissShaderTable = sbtImpl->miss.region; + desc.CallableShaderTable = sbtImpl->callable.region; + cmdBufferImpl->handle->lpVtbl->DispatchRays(cmdBufferImpl->handle, &desc); +} + +void PAL_CALL cmdTraceRaysIndirectD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer) +{ + HRESULT result; + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + DeviceD3D12* device = cmdBufferImpl->device; + ShaderBindingTableD3D12* sbtImpl = (ShaderBindingTableD3D12*)sbt; + BufferD3D12* bufferImpl = (BufferD3D12*)buffer; + D3D12_DISPATCH_RAYS_DESC desc = {0}; + + // we need to make sure the SBT is up to date + commitShaderbindingTableUpdateD3D12(cmdBufferImpl, sbtImpl); + + // BufferD3D12 is mappable + void* ptr = nullptr; + result = bufferImpl->handle->lpVtbl->Map(bufferImpl->handle, 0, nullptr, &ptr); + if (FAILED(result)) { + return; + } + + // copy indirect parameters from the buffer + PalDispatchIndirectData data = {0}; + memcpy(&data, ptr, sizeof(PalDispatchIndirectData)); + bufferImpl->handle->lpVtbl->Unmap(bufferImpl->handle, 0, nullptr); + + desc.Width = data.groupCountXOrWidth; + desc.Height = data.groupCountXOrHeight; + desc.Depth = data.groupCountXOrDepth; + + uint64_t stride = sbtImpl->raygen.region.StrideInBytes; + D3D12_GPU_VIRTUAL_ADDRESS_RANGE raygenAddress = {0}; + raygenAddress.SizeInBytes = sbtImpl->raygen.region.SizeInBytes; + raygenAddress.StartAddress = sbtImpl->baseAddress + raygenIndex * stride; + + desc.RayGenerationShaderRecord = raygenAddress; + desc.HitGroupTable = sbtImpl->hit.region; + desc.MissShaderTable = sbtImpl->miss.region; + desc.CallableShaderTable = sbtImpl->callable.region; + + // fill the data into the tmp upload buffer of the command buffer + ptr = nullptr; + cmdBufferImpl->stagingBuffer->lpVtbl->Map(cmdBufferImpl->stagingBuffer, 0, nullptr, &ptr); + memcpy(ptr, &desc, sizeof(D3D12_DISPATCH_RAYS_DESC)); + cmdBufferImpl->stagingBuffer->lpVtbl->Unmap(cmdBufferImpl->stagingBuffer, 0, nullptr); + + // copy to the gpu tmp buffer of the command buffer and execute with that + cmdBufferImpl->handle->lpVtbl->CopyBufferRegion( + cmdBufferImpl->handle, + cmdBufferImpl->buffer, + 0, + cmdBufferImpl->stagingBuffer, + 0, + sizeof(D3D12_DISPATCH_RAYS_DESC)); + + // put a memory barrier + D3D12_RESOURCE_BARRIER barrier = {0}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barrier.Transition.pResource = cmdBufferImpl->buffer; + cmdBufferImpl->handle->lpVtbl->ResourceBarrier(cmdBufferImpl->handle, 1, &barrier); + + cmdBufferImpl->handle->lpVtbl->ExecuteIndirect( + cmdBufferImpl->handle, + device->raySignature, + 1, + cmdBufferImpl->buffer, + 0, + nullptr, + 0); +} + +void PAL_CALL cmdBindDescriptorSetD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + PipelineD3D12* pipeline = cmdBufferImpl->pipeline; + DescriptorSetD3D12* setImpl = (DescriptorSetD3D12*)set; + DescriptorPoolD3D12* pool = setImpl->pool; + + // bind heaps + uint32_t heapCount = 0; + ID3D12DescriptorHeap* heaps[2]; + if (pool->hasResourceHeap) { + heaps[heapCount++] = pool->resourceHeap.handle; + } + + if (pool->hasSamplerHeap) { + heaps[heapCount++] = pool->samplerHeap.handle; + } + cmdBufferImpl->handle->lpVtbl->SetDescriptorHeaps(cmdBufferImpl->handle, heapCount, heaps); + + // bind resource descriptor table + uint32_t resourceCount = setImpl->layout->bindingCount - setImpl->layout->samplerCount; + uint32_t baseIndex = setIndex; + if (pipeline->layout->constantIndex != UINT32_MAX) { + // If push constant was used to create the pipeline layout + // slot 0 will be reserve for it + baseIndex++; + } + + if (resourceCount) { + D3D12_GPU_DESCRIPTOR_HANDLE base; + base.ptr = getDescriptorHandleD3D12( + setImpl->resourceOffset, + pool->resourceHeap.incrementSize, + pool->resourceHeap.gpuBase); + + if (pipeline->type == GRAPHICS_PIPELINE) { + cmdBufferImpl->handle->lpVtbl->SetGraphicsRootDescriptorTable( + cmdBufferImpl->handle, + baseIndex, // base set index is resource first before sampler + base); + + } else { + // ray tracing uses the compute path + cmdBufferImpl->handle->lpVtbl->SetComputeRootDescriptorTable( + cmdBufferImpl->handle, + baseIndex, // base set index is resource first before sampler + base); + } + + baseIndex++; + } + + // bind sampler descriptor table + if (setImpl->layout->samplerCount) { + D3D12_GPU_DESCRIPTOR_HANDLE base; + base.ptr = getDescriptorHandleD3D12( + setImpl->samplerOffset, + pool->samplerHeap.incrementSize, + pool->samplerHeap.gpuBase); + + if (pipeline->type == GRAPHICS_PIPELINE) { + cmdBufferImpl->handle->lpVtbl->SetGraphicsRootDescriptorTable( + cmdBufferImpl->handle, + baseIndex, + base); + + } else { + // ray tracing uses the compute path + cmdBufferImpl->handle->lpVtbl->SetComputeRootDescriptorTable( + cmdBufferImpl->handle, + baseIndex, + base); + } + } +} + +void PAL_CALL cmdPushConstantsD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value) +{ + CommandBufferD3D12* cmdBufferImpl = (CommandBufferD3D12*)cmdBuffer; + PipelineD3D12* pipeline = cmdBufferImpl->pipeline; + if (pipeline->layout->constantIndex != UINT32_MAX) { + if (pipeline->type == GRAPHICS_PIPELINE) { + cmdBufferImpl->handle->lpVtbl->SetGraphicsRoot32BitConstants( + cmdBufferImpl->handle, + pipeline->layout->constantIndex, + size / 4, + value, + offset / 4); + + } else { + // ray tracing uses the compute path + cmdBufferImpl->handle->lpVtbl->SetComputeRoot32BitConstants( + cmdBufferImpl->handle, + pipeline->layout->constantIndex, + size / 4, + value, + offset / 4); + } + } +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_d3d12.c b/src/graphics/d3d12/pal_d3d12.c new file mode 100644 index 00000000..d1d4803c --- /dev/null +++ b/src/graphics/d3d12/pal_d3d12.c @@ -0,0 +1,1163 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +// clang-format off +// IIDS +IID IID_Device = {0xc4fec28f, 0x7966, 0x4e95, 0x9f, 0x94, 0xf4, 0x31, 0xcb, 0x56, 0xc3, 0xb8}; +IID IID_Adapter = {0x3c8d99d1, 0x4fbf, 0x4181, 0xa8, 0x2c, 0xaf, 0x66, 0xbf, 0x7b, 0xd2, 0x4e}; +IID IID_Factory = {0xc1b6694f, 0xff09, 0x44a9, 0xb0, 0x3c, 0x77, 0x90, 0x0a, 0x0a, 0x1d, 0x17}; +IID IID_Debug = {0x344488b7, 0x6846, 0x474b, 0xb9, 0x89, 0xf0, 0x27, 0x44, 0x82, 0x45, 0xe0}; +IID IID_Debug1 = {0xaffaa4ca, 0x63fe, 0x4d8e, 0xb8, 0xad, 0x15, 0x90, 0x00, 0xaf, 0x43, 0x04}; +IID IID_InfoQueue = {0x0742a90b, 0xc387, 0x483f, 0xb9, 0x46, 0x30, 0xa7, 0xe4, 0xe6, 0x14, 0x58}; +IID IID_Heap = {0x6b3b2502, 0x6e51, 0x45b3, 0x90, 0xee, 0x98, 0x84, 0x26, 0x5e, 0x8d, 0xf3}; +IID IID_Queue = {0x0ec870a6, 0x5d7e, 0x4c22, 0x8c, 0xfc, 0x5b, 0xaa, 0xe0, 0x76, 0x16, 0xed}; +IID IID_Swapchain = {0x94d99bdb, 0xf1f8, 0x4ab0, 0xb2, 0x36, 0x7d, 0xa0, 0x17, 0x0e, 0xda, 0xb1}; +IID IID_CommandAllocator = {0x6102dee4, 0xaf59, 0x4b09, 0xb9, 0x99, 0xb4, 0x4d, 0x73, 0xf0, 0x9b, 0x24}; +IID IID_CommandList = {0x7116d91c, 0xe7e4, 0x47ce, 0xb8, 0xc6, 0xec, 0x81, 0x68, 0xf4, 0x37, 0xe5}; +IID IID_CommandList6 = {0xc3827890, 0xe548, 0x4cfa, 0x96, 0xcf, 0x56, 0x89, 0xa9, 0x37, 0x0f, 0x80}; +IID IID_CommandSignature = {0xc36a797c, 0xec80, 0x4f0a, 0x89, 0x85, 0xa7, 0xb2, 0x47, 0x50, 0x82, 0xd1}; +IID IID_DescriptorHeap = {0x8efb471d, 0x616c, 0x4f49, 0x90, 0xf7, 0x12, 0x7b, 0xb7, 0x63, 0xfa, 0x51}; +IID IID_RootSignature = {0xc54a6b66, 0x72df, 0x4ee8, 0x8b, 0xe5, 0xa9, 0x46, 0xa1, 0x42, 0x92, 0x14}; +IID IID_Device5 = {0x8b4f173b, 0x2fea, 0x4b80, 0x8f, 0x58, 0x43, 0x07, 0x19, 0x1a, 0xb9, 0x5d}; +IID IID_PipelineState = {0x765a30f3, 0xf624, 0x4c6f, 0xa8, 0x28, 0xac, 0xe9, 0x48, 0x62, 0x24, 0x45}; +IID IID_StateObject = {0x47016943, 0xfca8, 0x4594, 0x93, 0xea, 0xaf, 0x25, 0x8b, 0x55, 0x34, 0x6d}; +IID IID_Resource = {0x696442be, 0xa72e, 0x4059, 0xbc, 0x79, 0x5b, 0x5c, 0x98, 0x04, 0x0f, 0xad}; +IID IID_StateObjectProps = {0xde5fa827, 0x9bf9, 0x4f26, 0x89, 0xff, 0xd7, 0xf5, 0x6f, 0xde, 0x38, 0x60}; +IID IID_Fence = {0x0a753dcf, 0xc4d8, 0x4b91, 0xad, 0xf6, 0xbe, 0x5a, 0x60, 0xd9, 0x5a, 0x76}; +// clang-format on + +D3D12 s_D3D12 = {0}; + +PalResult makeResultD3D12(HRESULT result) +{ + PalResultCode code = PAL_RESULT_CODE_PLATFORM_FAILURE; + switch (result) { + case E_NOTIMPL: + case DXGI_ERROR_UNSUPPORTED: { + code = PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + break; + } + + case E_OUTOFMEMORY: { + code = PAL_RESULT_CODE_OUT_OF_MEMORY; + break; + } + + case E_ACCESSDENIED: { + code = PAL_RESULT_CODE_INVALID_OPERATION; + break; + } + + case DXGI_ERROR_DEVICE_HUNG: + case DXGI_ERROR_DEVICE_REMOVED: + case DXGI_ERROR_DEVICE_RESET: { + code = PAL_RESULT_CODE_DEVICE_LOST; + break; + } + + case E_INVALIDARG: { + code = PAL_RESULT_CODE_INVALID_ARGUMENT; + break; + } + } + + return palMakeResult(code, PAL_RESULT_SOURCE_D3D12, (uint32_t)result); +} + +DXGI_FORMAT formatToD3D12(PalFormat format) +{ + switch (format) { + case PAL_FORMAT_R8_UNORM: + return DXGI_FORMAT_R8_UNORM; + + case PAL_FORMAT_R8_SNORM: + return DXGI_FORMAT_R8_SNORM; + + case PAL_FORMAT_R8_UINT: + return DXGI_FORMAT_R8_UINT; + + case PAL_FORMAT_R8_SINT: + return DXGI_FORMAT_R8_SINT; + + case PAL_FORMAT_R8_SRGB: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16_UNORM: + return DXGI_FORMAT_R16_UNORM; + + case PAL_FORMAT_R16_SNORM: + return DXGI_FORMAT_R16_SNORM; + + case PAL_FORMAT_R16_UINT: + return DXGI_FORMAT_R16_UINT; + + case PAL_FORMAT_R16_SINT: + return DXGI_FORMAT_R16_SINT; + + case PAL_FORMAT_R16_SFLOAT: + return DXGI_FORMAT_R16_FLOAT; + + case PAL_FORMAT_R32_UINT: + return DXGI_FORMAT_R32_UINT; + + case PAL_FORMAT_R32_SINT: + return DXGI_FORMAT_R32_SINT; + + case PAL_FORMAT_R32_SFLOAT: + return DXGI_FORMAT_R32_FLOAT; + + case PAL_FORMAT_R64_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64_SFLOAT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8_UNORM: + return DXGI_FORMAT_R8G8_UNORM; + + case PAL_FORMAT_R8G8_SNORM: + return DXGI_FORMAT_R8G8_SNORM; + + case PAL_FORMAT_R8G8_UINT: + return DXGI_FORMAT_R8G8_UINT; + + case PAL_FORMAT_R8G8_SINT: + return DXGI_FORMAT_R8G8_SINT; + + case PAL_FORMAT_R8G8_SRGB: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16_UNORM: + return DXGI_FORMAT_R16G16_UNORM; + + case PAL_FORMAT_R16G16_SNORM: + return DXGI_FORMAT_R16G16_SNORM; + + case PAL_FORMAT_R16G16_UINT: + return DXGI_FORMAT_R16G16_UINT; + + case PAL_FORMAT_R16G16_SINT: + return DXGI_FORMAT_R16G16_SINT; + + case PAL_FORMAT_R16G16_SFLOAT: + return DXGI_FORMAT_R16G16_FLOAT; + + case PAL_FORMAT_R32G32_UINT: + return DXGI_FORMAT_R32G32_UINT; + + case PAL_FORMAT_R32G32_SINT: + return DXGI_FORMAT_R32G32_SINT; + + case PAL_FORMAT_R32G32_SFLOAT: + return DXGI_FORMAT_R32G32_FLOAT; + + case PAL_FORMAT_R64G64_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64_SFLOAT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8_UNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8_SNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8_SRGB: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16B16_UNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16B16_SNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16B16_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16B16_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R16G16B16_SFLOAT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R32G32B32_UINT: + return DXGI_FORMAT_R32G32B32_UINT; + + case PAL_FORMAT_R32G32B32_SINT: + return DXGI_FORMAT_R32G32B32_SINT; + + case PAL_FORMAT_R32G32B32_SFLOAT: + return DXGI_FORMAT_R32G32B32_FLOAT; + + case PAL_FORMAT_R64G64B64_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64B64_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64B64_SFLOAT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8_UNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8_SNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8_SRGB: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R8G8B8A8_UNORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + + case PAL_FORMAT_R8G8B8A8_SNORM: + return DXGI_FORMAT_R8G8B8A8_SNORM; + + case PAL_FORMAT_R8G8B8A8_UINT: + return DXGI_FORMAT_R8G8B8A8_UINT; + + case PAL_FORMAT_R8G8B8A8_SINT: + return DXGI_FORMAT_R8G8B8A8_SINT; + + case PAL_FORMAT_R8G8B8A8_SRGB: + return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + + case PAL_FORMAT_R16G16B16A16_UNORM: + return DXGI_FORMAT_R16G16B16A16_UNORM; + + case PAL_FORMAT_R16G16B16A16_SNORM: + return DXGI_FORMAT_R16G16B16A16_SNORM; + + case PAL_FORMAT_R16G16B16A16_UINT: + return DXGI_FORMAT_R16G16B16A16_UINT; + + case PAL_FORMAT_R16G16B16A16_SINT: + return DXGI_FORMAT_R16G16B16A16_SINT; + + case PAL_FORMAT_R16G16B16A16_SFLOAT: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + + case PAL_FORMAT_R32G32B32A32_UINT: + return DXGI_FORMAT_R32G32B32A32_UINT; + + case PAL_FORMAT_R32G32B32A32_SINT: + return DXGI_FORMAT_R32G32B32A32_SINT; + + case PAL_FORMAT_R32G32B32A32_SFLOAT: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + + case PAL_FORMAT_R64G64B64A64_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64B64A64_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_R64G64B64A64_SFLOAT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8A8_UNORM: + return DXGI_FORMAT_B8G8R8A8_UNORM; + + case PAL_FORMAT_B8G8R8A8_SNORM: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8A8_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8A8_SINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_B8G8R8A8_SRGB: + return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + + case PAL_FORMAT_S8_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_D16_UNORM: + return DXGI_FORMAT_D16_UNORM; + + case PAL_FORMAT_D32_SFLOAT: + return DXGI_FORMAT_D32_FLOAT; + + case PAL_FORMAT_D32_SFLOAT_S8_UINT: + return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; + + case PAL_FORMAT_D16_UNORM_S8_UINT: + return DXGI_FORMAT_UNKNOWN; + + case PAL_FORMAT_D24_UNORM_S8_UINT: + return DXGI_FORMAT_D24_UNORM_S8_UINT; + } + + return DXGI_FORMAT_UNKNOWN; +} + +uint32_t samplesToD3D12(PalSampleCount count) +{ + switch (count) { + case PAL_SAMPLE_COUNT_2: + return 2; + + case PAL_SAMPLE_COUNT_4: + return 4; + + case PAL_SAMPLE_COUNT_8: + return 8; + + case PAL_SAMPLE_COUNT_16: + return 16; + + case PAL_SAMPLE_COUNT_32: + return 32; + + case PAL_SAMPLE_COUNT_64: + return 64; + } + + return 1; +} + +D3D12_COMPARISON_FUNC compareOpToD3D12(PalCompareOp op) +{ + switch (op) { + case PAL_COMPARE_OP_NEVER: + return D3D12_COMPARISON_FUNC_NEVER; + + case PAL_COMPARE_OP_LESS: + return D3D12_COMPARISON_FUNC_LESS; + + case PAL_COMPARE_OP_EQUAL: + return D3D12_COMPARISON_FUNC_EQUAL; + + case PAL_COMPARE_OP_LESS_OR_EQUAL: + return D3D12_COMPARISON_FUNC_LESS_EQUAL; + + case PAL_COMPARE_OP_GREATER: + return D3D12_COMPARISON_FUNC_GREATER; + + case PAL_COMPARE_OP_NOT_EQUAL: + return D3D12_COMPARISON_FUNC_NOT_EQUAL; + + case PAL_COMPARE_OP_GREATER_OR_EQUAL: + return D3D12_COMPARISON_FUNC_GREATER_EQUAL; + + case PAL_COMPARE_OP_ALWAYS: + return D3D12_COMPARISON_FUNC_ALWAYS; + } + + return D3D12_COMPARISON_FUNC_NEVER; +} + +D3D12_SHADING_RATE_COMBINER combinerOpsToD3D12(PalFragmentShadingRateCombinerOp op) +{ + switch (op) { + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP: + return D3D12_SHADING_RATE_COMBINER_PASSTHROUGH; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE: + return D3D12_SHADING_RATE_COMBINER_OVERRIDE; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN: + return D3D12_SHADING_RATE_COMBINER_MIN; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX: + return D3D12_SHADING_RATE_COMBINER_MAX; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL: + return D3D12_SHADING_RATE_COMBINER_SUM; + } + + return D3D12_SHADING_RATE_COMBINER_PASSTHROUGH; +} + +D3D12_SHADING_RATE shadingRateToD3D12(PalFragmentShadingRate rate) +{ + switch (rate) { + case PAL_FRAGMENT_SHADING_RATE_1X1: + return D3D12_SHADING_RATE_1X1; + + case PAL_FRAGMENT_SHADING_RATE_1X2: + return D3D12_SHADING_RATE_1X2; + + case PAL_FRAGMENT_SHADING_RATE_2X1: + return D3D12_SHADING_RATE_2X1; + + case PAL_FRAGMENT_SHADING_RATE_2X2: + return D3D12_SHADING_RATE_2X2; + + case PAL_FRAGMENT_SHADING_RATE_2X4: + return D3D12_SHADING_RATE_2X4; + + case PAL_FRAGMENT_SHADING_RATE_4X2: + return D3D12_SHADING_RATE_4X2; + + case PAL_FRAGMENT_SHADING_RATE_4X4: + return D3D12_SHADING_RATE_4X4; + } + + return D3D12_SHADING_RATE_1X1; +} + +uint32_t getFormatSizeD3D12(PalFormat format) +{ + switch (format) { + case PAL_FORMAT_R8_UNORM: + case PAL_FORMAT_R8_SNORM: + case PAL_FORMAT_R8_UINT: + case PAL_FORMAT_R8_SINT: + case PAL_FORMAT_R8_SRGB: + case PAL_FORMAT_S8_UINT: + return 1; + + case PAL_FORMAT_R16_UNORM: + case PAL_FORMAT_R16_SNORM: + case PAL_FORMAT_R16_UINT: + case PAL_FORMAT_R16_SINT: + case PAL_FORMAT_R16_SFLOAT: + case PAL_FORMAT_R8G8_UNORM: + case PAL_FORMAT_R8G8_SNORM: + case PAL_FORMAT_R8G8_UINT: + case PAL_FORMAT_R8G8_SINT: + case PAL_FORMAT_R8G8_SRGB: + case PAL_FORMAT_D16_UNORM: + return 2; + + case PAL_FORMAT_R8G8B8_UNORM: + case PAL_FORMAT_R8G8B8_SNORM: + case PAL_FORMAT_R8G8B8_UINT: + case PAL_FORMAT_R8G8B8_SINT: + case PAL_FORMAT_R8G8B8_SRGB: + case PAL_FORMAT_B8G8R8_UNORM: + case PAL_FORMAT_B8G8R8_SNORM: + case PAL_FORMAT_B8G8R8_UINT: + case PAL_FORMAT_B8G8R8_SINT: + case PAL_FORMAT_B8G8R8_SRGB: + case PAL_FORMAT_D16_UNORM_S8_UINT: + return 3; + + case PAL_FORMAT_R32_UINT: + case PAL_FORMAT_R32_SINT: + case PAL_FORMAT_R32_SFLOAT: + case PAL_FORMAT_R16G16_UNORM: + case PAL_FORMAT_R16G16_SNORM: + case PAL_FORMAT_R16G16_UINT: + case PAL_FORMAT_R16G16_SINT: + case PAL_FORMAT_R16G16_SFLOAT: + case PAL_FORMAT_R8G8B8A8_UNORM: + case PAL_FORMAT_R8G8B8A8_SNORM: + case PAL_FORMAT_R8G8B8A8_UINT: + case PAL_FORMAT_R8G8B8A8_SINT: + case PAL_FORMAT_R8G8B8A8_SRGB: + case PAL_FORMAT_B8G8R8A8_UNORM: + case PAL_FORMAT_B8G8R8A8_SNORM: + case PAL_FORMAT_B8G8R8A8_UINT: + case PAL_FORMAT_B8G8R8A8_SINT: + case PAL_FORMAT_B8G8R8A8_SRGB: + case PAL_FORMAT_D32_SFLOAT: + case PAL_FORMAT_D24_UNORM_S8_UINT: + return 4; + + case PAL_FORMAT_D32_SFLOAT_S8_UINT: + return 5; + + case PAL_FORMAT_R16G16B16_UNORM: + case PAL_FORMAT_R16G16B16_SNORM: + case PAL_FORMAT_R16G16B16_UINT: + case PAL_FORMAT_R16G16B16_SINT: + case PAL_FORMAT_R16G16B16_SFLOAT: + return 6; + + case PAL_FORMAT_R64_UINT: + case PAL_FORMAT_R64_SINT: + case PAL_FORMAT_R64_SFLOAT: + case PAL_FORMAT_R32G32_UINT: + case PAL_FORMAT_R32G32_SINT: + case PAL_FORMAT_R32G32_SFLOAT: + case PAL_FORMAT_R16G16B16A16_UNORM: + case PAL_FORMAT_R16G16B16A16_SNORM: + case PAL_FORMAT_R16G16B16A16_UINT: + case PAL_FORMAT_R16G16B16A16_SINT: + case PAL_FORMAT_R16G16B16A16_SFLOAT: + return 8; + + case PAL_FORMAT_R32G32B32_UINT: + case PAL_FORMAT_R32G32B32_SINT: + case PAL_FORMAT_R32G32B32_SFLOAT: + return 12; + + case PAL_FORMAT_R64G64_UINT: + case PAL_FORMAT_R64G64_SINT: + case PAL_FORMAT_R64G64_SFLOAT: + case PAL_FORMAT_R32G32B32A32_UINT: + case PAL_FORMAT_R32G32B32A32_SINT: + case PAL_FORMAT_R32G32B32A32_SFLOAT: + return 16; + + case PAL_FORMAT_R64G64B64_UINT: + case PAL_FORMAT_R64G64B64_SINT: + case PAL_FORMAT_R64G64B64_SFLOAT: + return 24; + + case PAL_FORMAT_R64G64B64A64_UINT: + case PAL_FORMAT_R64G64B64A64_SINT: + case PAL_FORMAT_R64G64B64A64_SFLOAT: + return 32; + } + + return 0; +} + +DXGI_FORMAT vertexTypeToD3D12(PalVertexType type) +{ + switch (type) { + case PAL_VERTEX_TYPE_INT32: + return DXGI_FORMAT_R32_SINT; + + case PAL_VERTEX_TYPE_INT32_2: + return DXGI_FORMAT_R32G32_SINT; + + case PAL_VERTEX_TYPE_INT32_3: + return DXGI_FORMAT_R32G32B32_SINT; + + case PAL_VERTEX_TYPE_INT32_4: + return DXGI_FORMAT_R32G32B32A32_SINT; + + case PAL_VERTEX_TYPE_UINT32: + return DXGI_FORMAT_R32_UINT; + + case PAL_VERTEX_TYPE_UINT32_2: + return DXGI_FORMAT_R32G32_UINT; + + case PAL_VERTEX_TYPE_UINT32_3: + return DXGI_FORMAT_R32G32B32_UINT; + + case PAL_VERTEX_TYPE_UINT32_4: + return DXGI_FORMAT_R32G32B32A32_UINT; + + case PAL_VERTEX_TYPE_INT8_2: + return DXGI_FORMAT_R8G8_SINT; + + case PAL_VERTEX_TYPE_INT8_4: + return DXGI_FORMAT_R8G8B8A8_SINT; + + case PAL_VERTEX_TYPE_UINT8_2: + return DXGI_FORMAT_R8G8_UINT; + + case PAL_VERTEX_TYPE_UINT8_4: + return DXGI_FORMAT_R8G8B8A8_UINT; + + case PAL_VERTEX_TYPE_INT8_2NORM: + return DXGI_FORMAT_R8G8_SNORM; + + case PAL_VERTEX_TYPE_INT8_4NORM: + return DXGI_FORMAT_R8G8B8A8_SNORM; + + case PAL_VERTEX_TYPE_UINT8_2NORM: + return DXGI_FORMAT_R8G8_UNORM; + + case PAL_VERTEX_TYPE_UINT8_4NORM: + return DXGI_FORMAT_R8G8B8A8_UNORM; + + case PAL_VERTEX_TYPE_INT16_2: + return DXGI_FORMAT_R16G16_SINT; + + case PAL_VERTEX_TYPE_INT16_4: + return DXGI_FORMAT_R16G16B16A16_SINT; + + case PAL_VERTEX_TYPE_UINT16_2: + return DXGI_FORMAT_R16G16_UINT; + + case PAL_VERTEX_TYPE_UINT16_4: + return DXGI_FORMAT_R16G16B16A16_UINT; + + case PAL_VERTEX_TYPE_INT16_2NORM: + return DXGI_FORMAT_R16G16_SNORM; + + case PAL_VERTEX_TYPE_INT16_4NORM: + return DXGI_FORMAT_R16G16B16A16_SNORM; + + case PAL_VERTEX_TYPE_UINT16_2NORM: + return DXGI_FORMAT_R16G16_UNORM; + + case PAL_VERTEX_TYPE_UINT16_4NORM: + return DXGI_FORMAT_R16G16B16A16_UNORM; + + case PAL_VERTEX_TYPE_FLOAT: + return DXGI_FORMAT_R32_FLOAT; + + case PAL_VERTEX_TYPE_FLOAT2: + return DXGI_FORMAT_R32G32_FLOAT; + + case PAL_VERTEX_TYPE_FLOAT3: + return DXGI_FORMAT_R32G32B32_FLOAT; + + case PAL_VERTEX_TYPE_FLOAT4: + return DXGI_FORMAT_R32G32B32A32_FLOAT; + + case PAL_VERTEX_TYPE_HALF_FLOAT16_2: + return DXGI_FORMAT_R16G16_FLOAT; + + case PAL_VERTEX_TYPE_HALF_FLOAT16_4: + return DXGI_FORMAT_R16G16B16A16_FLOAT; + } + + return DXGI_FORMAT_UNKNOWN; +} + +D3D12_RAYTRACING_INSTANCE_FLAGS instanceFlagsToD3D12(PalAccelerationStructureInstanceFlags flags) +{ + D3D12_RAYTRACING_INSTANCE_FLAGS instanceFlags = 0; + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_OPAQUE) { + instanceFlags |= D3D12_RAYTRACING_INSTANCE_FLAG_FORCE_OPAQUE; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_NO_OPAQUE) { + instanceFlags |= D3D12_RAYTRACING_INSTANCE_FLAG_FORCE_NON_OPAQUE; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FACING_CULL_DISABLE) { + instanceFlags |= D3D12_RAYTRACING_INSTANCE_FLAG_TRIANGLE_CULL_DISABLE; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FRONT_COUNTERCLOCKWISE) { + instanceFlags |= D3D12_RAYTRACING_INSTANCE_FLAG_TRIANGLE_FRONT_COUNTERCLOCKWISE; + } + + return instanceFlags; +} + +void fillBuildInfoD3D12( + PalBool getBuildSize, + PalAccelerationStructureBuildInfo* info, + D3D12_RAYTRACING_GEOMETRY_DESC* geometries, + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC* buildInfo) +{ + static uint32_t maxInstanceCount = 1000000; + static uint32_t maxPrimitiveCount = 10000000; + static uint32_t maxGeometryCount = 100000; + + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL) { + if (info->count > maxGeometryCount) { + return; + } + + for (int i = 0; i < info->count; i++) { + if (info->geometries[i].primitiveCount > maxPrimitiveCount) { + return; + } + + D3D12_RAYTRACING_GEOMETRY_DESC* tmp = &geometries[i]; + tmp->Flags = 0; + if (info->geometries[i].flags & PAL_GEOMETRY_FLAG_NO_DUPLICATE_ANYHIT) { + tmp->Flags |= D3D12_RAYTRACING_GEOMETRY_FLAG_NO_DUPLICATE_ANYHIT_INVOCATION; + } + + if (info->geometries[i].flags & PAL_GEOMETRY_FLAG_OPAQUE) { + tmp->Flags |= D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE; + } + + if (info->geometries[i].type == PAL_GEOMETRY_TYPE_TRIANGLE) { + tmp->Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + const PalGeometryDataTriangle* tmpData = info->geometries[i].data; + + tmp->Triangles.VertexBuffer.StartAddress = tmpData->vertexBufferAddress; + tmp->Triangles.VertexBuffer.StrideInBytes = tmpData->vertexStride; + tmp->Triangles.VertexCount = tmpData->vertexCount; + tmp->Triangles.VertexFormat = vertexTypeToD3D12(tmpData->vertexType); + + tmp->Triangles.Transform3x4 = tmpData->transformBufferAddress; + + tmp->Triangles.IndexBuffer = tmpData->indexBufferAddress; + tmp->Triangles.IndexCount = (UINT)info->geometries[i].primitiveCount * 3; + if (tmpData->indexType == PAL_INDEX_TYPE_UINT16) { + if (tmp->Triangles.IndexBuffer) { + tmp->Triangles.IndexFormat = DXGI_FORMAT_R16_FLOAT; + } + + } else { + if (tmp->Triangles.IndexBuffer) { + tmp->Triangles.IndexFormat = DXGI_FORMAT_R32_FLOAT; + } + } + + } else if (info->geometries[i].type == PAL_GEOMETRY_TYPE_AABBS) { + tmp->Type = D3D12_RAYTRACING_GEOMETRY_TYPE_PROCEDURAL_PRIMITIVE_AABBS; + const PalGeometryDataAABBS* tmpData = info->geometries[i].data; + + tmp->AABBs.AABBCount = info->geometries[i].primitiveCount; + tmp->AABBs.AABBs.StartAddress = tmpData->bufferAddress; + tmp->AABBs.AABBs.StrideInBytes = tmpData->stride; + } + } + + buildInfo->Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + buildInfo->Inputs.NumDescs = info->count; + buildInfo->Inputs.pGeometryDescs = geometries; + + } else { + if (info->count > maxInstanceCount) { + return; + } + + buildInfo->Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + buildInfo->Inputs.NumDescs = info->count; + buildInfo->Inputs.InstanceDescs = info->instanceBufferAddress; + } + + // build mode + buildInfo->Inputs.Flags = 0; + if (info->buildMode == PAL_ACCELERATION_STRUCTURE_BUILD_MODE_UPDATE) { + buildInfo->Inputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_ALLOW_UPDATE; + } + + // build hints + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_BUILD) { + buildInfo->Inputs.Flags |= + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_BUILD; + } + + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_TRACE) { + buildInfo->Inputs.Flags |= + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; + } + + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_LOW_MEMORY) { + buildInfo->Inputs.Flags |= + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_MINIMIZE_MEMORY; + } + + AccelerationStructureD3D12* dstAs = (AccelerationStructureD3D12*)info->dst; + AccelerationStructureD3D12* srcAs = (AccelerationStructureD3D12*)info->src; + + D3D12_GPU_VIRTUAL_ADDRESS dstAsHandle = 0; + if (dstAs) { + dstAsHandle = dstAs->address; + } + + D3D12_GPU_VIRTUAL_ADDRESS srcAsHandle = 0; + if (srcAs) { + srcAsHandle = srcAs->address; + } + + buildInfo->Inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + buildInfo->DestAccelerationStructureData = dstAsHandle; + buildInfo->SourceAccelerationStructureData = srcAsHandle; + buildInfo->ScratchAccelerationStructureData = info->scratchBufferAddress; +} + +void fillSubresourceD3D12( + uint32_t descType, + PalImageViewType type, + const PalImageSubresourceRange* range, + void* desc) +{ + if (descType == DESC_TYPE_RTV) { + D3D12_RENDER_TARGET_VIEW_DESC* rtvDesc = desc; + + if (type == PAL_IMAGE_VIEW_TYPE_1D) { + rtvDesc->Texture1D.MipSlice = range->startMipLevel; + rtvDesc->ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_1D_ARRAY) { + rtvDesc->Texture1DArray.MipSlice = range->startMipLevel; + rtvDesc->Texture1DArray.FirstArraySlice = range->startArrayLayer; + rtvDesc->Texture1DArray.ArraySize = range->layerArrayCount; + rtvDesc->ViewDimension = D3D12_RTV_DIMENSION_TEXTURE1DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D) { + rtvDesc->Texture2D.MipSlice = range->startMipLevel; + rtvDesc->ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D_ARRAY) { + rtvDesc->Texture2DArray.MipSlice = range->startMipLevel; + rtvDesc->Texture2DArray.FirstArraySlice = range->startArrayLayer; + rtvDesc->Texture2DArray.ArraySize = range->layerArrayCount; + rtvDesc->ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_3D) { + rtvDesc->Texture3D.MipSlice = range->startMipLevel; + rtvDesc->Texture3D.FirstWSlice = range->startArrayLayer; + rtvDesc->Texture3D.WSize = range->layerArrayCount; + rtvDesc->ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D; + } + + } else if (descType == DESC_TYPE_DSV) { + D3D12_DEPTH_STENCIL_VIEW_DESC* dsvDesc = desc; + + if (range->aspect == PAL_IMAGE_ASPECT_DEPTH) { + dsvDesc->Flags = D3D12_DSV_FLAG_READ_ONLY_DEPTH; + } else if (range->aspect == PAL_IMAGE_ASPECT_STENCIL) { + dsvDesc->Flags = D3D12_DSV_FLAG_READ_ONLY_STENCIL; + } + + if (type == PAL_IMAGE_VIEW_TYPE_1D) { + dsvDesc->Texture1D.MipSlice = range->startMipLevel; + dsvDesc->ViewDimension = D3D12_DSV_DIMENSION_TEXTURE1D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_1D_ARRAY) { + dsvDesc->Texture1DArray.MipSlice = range->startMipLevel; + dsvDesc->Texture1DArray.FirstArraySlice = range->startArrayLayer; + dsvDesc->Texture1DArray.ArraySize = range->layerArrayCount; + dsvDesc->ViewDimension = D3D12_DSV_DIMENSION_TEXTURE1DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D) { + dsvDesc->Texture2D.MipSlice = range->startMipLevel; + dsvDesc->ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D_ARRAY) { + dsvDesc->Texture2DArray.MipSlice = range->startMipLevel; + dsvDesc->Texture2DArray.FirstArraySlice = range->startArrayLayer; + dsvDesc->Texture2DArray.ArraySize = range->layerArrayCount; + dsvDesc->ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + } + + } else if (descType == DESC_TYPE_SRV) { + D3D12_SHADER_RESOURCE_VIEW_DESC* srvDesc = desc; + + if (type == PAL_IMAGE_VIEW_TYPE_1D) { + srvDesc->Texture1D.MipLevels = range->mipLevelCount; + srvDesc->Texture1D.MostDetailedMip = range->startMipLevel; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_1D_ARRAY) { + srvDesc->Texture1DArray.MipLevels = range->mipLevelCount; + srvDesc->Texture1DArray.MostDetailedMip = range->startMipLevel; + srvDesc->Texture1DArray.FirstArraySlice = range->startArrayLayer; + srvDesc->Texture1DArray.ArraySize = range->layerArrayCount; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D) { + srvDesc->Texture2D.MipLevels = range->mipLevelCount; + srvDesc->Texture2D.MostDetailedMip = range->startMipLevel; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D_ARRAY) { + srvDesc->Texture2DArray.MipLevels = range->mipLevelCount; + srvDesc->Texture2DArray.MostDetailedMip = range->startMipLevel; + srvDesc->Texture2DArray.FirstArraySlice = range->startArrayLayer; + srvDesc->Texture2DArray.ArraySize = range->layerArrayCount; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_3D) { + srvDesc->Texture3D.MipLevels = range->mipLevelCount; + srvDesc->Texture3D.MostDetailedMip = range->startMipLevel; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_CUBE) { + srvDesc->TextureCube.MipLevels = range->mipLevelCount; + srvDesc->TextureCube.MostDetailedMip = range->startMipLevel; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + + } else if (type == PAL_IMAGE_VIEW_TYPE_CUBE_ARRAY) { + srvDesc->TextureCubeArray.MipLevels = range->mipLevelCount; + srvDesc->TextureCubeArray.MostDetailedMip = range->startMipLevel; + srvDesc->TextureCubeArray.First2DArrayFace = range->startArrayLayer; + srvDesc->TextureCubeArray.NumCubes = range->layerArrayCount; + srvDesc->ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY; + } + + } else if (descType == DESC_TYPE_UAV) { + D3D12_UNORDERED_ACCESS_VIEW_DESC* uavDesc = desc; + + if (type == PAL_IMAGE_VIEW_TYPE_1D) { + uavDesc->Texture1D.MipSlice = range->startMipLevel; + uavDesc->ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_1D_ARRAY) { + uavDesc->Texture1DArray.MipSlice = range->startMipLevel; + uavDesc->Texture1DArray.FirstArraySlice = range->startArrayLayer; + uavDesc->Texture1DArray.ArraySize = range->layerArrayCount; + uavDesc->ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D) { + uavDesc->Texture2D.MipSlice = range->startMipLevel; + uavDesc->ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + + } else if (type == PAL_IMAGE_VIEW_TYPE_2D_ARRAY) { + uavDesc->Texture2DArray.MipSlice = range->startMipLevel; + uavDesc->Texture2DArray.FirstArraySlice = range->startArrayLayer; + uavDesc->Texture2DArray.ArraySize = range->layerArrayCount; + uavDesc->ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + + } else if (type == PAL_IMAGE_VIEW_TYPE_3D) { + uavDesc->Texture3D.MipSlice = range->startMipLevel; + uavDesc->Texture3D.FirstWSlice = range->startArrayLayer; + uavDesc->Texture3D.WSize = range->layerArrayCount; + uavDesc->ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; + } + return; + } +} + +uint64_t getDescriptorHandleD3D12( + uint32_t index, + uint32_t size, + uint64_t baseOffset) +{ + return baseOffset + index * size; +} + +void getDescriptorTierLimitsD3D12( + void* device, + PalResourceCapabilities* caps, + PalDescriptorIndexingCapabilities* descCaps) +{ + D3D12_FEATURE_DATA_D3D12_OPTIONS options = {0}; + D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = {0}; + ID3D12Device* handle = device; + handle->lpVtbl + ->CheckFeatureSupport(handle, D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options)); + + handle->lpVtbl + ->CheckFeatureSupport(handle, D3D12_FEATURE_D3D12_OPTIONS5, &options5, sizeof(options5)); + + uint32_t perStage = 0; + if (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED) { + // add buckets for acceleration structure + if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_1) { + perStage = 8; + } else { + perStage = 5000; + } + } + + PalResourceCapabilities tmp = {0}; + if (options.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_1) { + tmp.maxPerStageSampledImages = 128 - perStage; + tmp.maxPerStageStorageImages = 4; + tmp.maxPerStageSamplers = 16; + tmp.maxPerStageStorageBuffers = 4; + tmp.maxPerStageUniformBuffers = 14; + tmp.maxPerStageAccelerationStructure = perStage; + + } else { + tmp.maxPerStageSampledImages = 705000 - perStage; + tmp.maxPerStageStorageImages = 100000; + tmp.maxPerStageSamplers = 2048; + tmp.maxPerStageStorageBuffers = 100000; + tmp.maxPerStageUniformBuffers = 95000; + tmp.maxPerStageAccelerationStructure = perStage; + } + + tmp.maxPerSetSampledImages = tmp.maxPerStageSampledImages; + tmp.maxPerSetStorageImages = tmp.maxPerStageStorageImages; + tmp.maxPerSetSamplers = tmp.maxPerStageSamplers; + tmp.maxPerSetStorageBuffers = tmp.maxPerStageStorageBuffers; + tmp.maxPerSetUniformBuffers = tmp.maxPerStageUniformBuffers; + tmp.maxPerSetAccelerationStructure = tmp.maxPerStageAccelerationStructure; + + if (caps) { + *caps = tmp; + } else { + descCaps->maxPerStageSampledImages = tmp.maxPerStageSampledImages; + descCaps->maxPerSetSampledImages = tmp.maxPerSetSampledImages; + descCaps->maxPerStageStorageImages = tmp.maxPerStageStorageImages; + descCaps->maxPerSetStorageImages = tmp.maxPerSetStorageImages; + descCaps->maxPerStageSamplers = tmp.maxPerStageSamplers; + descCaps->maxPerSetSamplers = tmp.maxPerSetSamplers; + descCaps->maxPerStageStorageBuffers = tmp.maxPerStageStorageBuffers; + descCaps->maxPerSetStorageBuffers = tmp.maxPerSetStorageBuffers; + descCaps->maxPerStageUniformBuffers = tmp.maxPerStageUniformBuffers; + descCaps->maxPerSetUniformBuffers = tmp.maxPerSetUniformBuffers; + descCaps->maxPerStageAccelerationStructure = tmp.maxPerStageAccelerationStructure; + descCaps->maxPerSetAccelerationStructure = tmp.maxPerSetAccelerationStructure; + } +} + +void pollMessagesD3D12(DeviceD3D12* device) +{ + ID3D12InfoQueue* queue = device->infoQueue; + if (!queue) { + return; + } + + UINT64 messageCount = queue->lpVtbl->GetNumStoredMessages(queue); + for (int i = 0; i < messageCount; i++) { + SIZE_T size = 16384; + uint8_t* buffer = device->scratchBuffer; + queue->lpVtbl->GetMessage(queue, i, (D3D12_MESSAGE*)buffer, &size); + + const char* message = ((D3D12_MESSAGE*)buffer)->pDescription; + D3D12_MESSAGE_CATEGORY category = ((D3D12_MESSAGE*)buffer)->Category; + D3D12_MESSAGE_SEVERITY severity = ((D3D12_MESSAGE*)buffer)->Severity; + + PalDebugMessageSeverity msgSeverity = 0; + PalDebugMessageType msgType = 0; + switch (category) { + case D3D12_MESSAGE_CATEGORY_APPLICATION_DEFINED: + case D3D12_MESSAGE_CATEGORY_MISCELLANEOUS: + case D3D12_MESSAGE_CATEGORY_COMPILATION: { + msgType = PAL_DEBUG_MESSAGE_TYPE_GENERAL; + break; + } + + case D3D12_MESSAGE_CATEGORY_INITIALIZATION: + case D3D12_MESSAGE_CATEGORY_SHADER: + case D3D12_MESSAGE_CATEGORY_RESOURCE_MANIPULATION: + case D3D12_MESSAGE_CATEGORY_EXECUTION: + case D3D12_MESSAGE_CATEGORY_STATE_GETTING: + case D3D12_MESSAGE_CATEGORY_STATE_SETTING: { + msgType = PAL_DEBUG_MESSAGE_TYPE_VALIDATION; + break; + } + } + + switch (severity) { + case D3D12_MESSAGE_SEVERITY_INFO: { + msgSeverity = PAL_DEBUG_MESSAGE_SEVERITY_INFO; + break; + } + + case D3D12_MESSAGE_SEVERITY_WARNING: { + msgSeverity = PAL_DEBUG_MESSAGE_SEVERITY_INFO; + break; + } + + case D3D12_MESSAGE_SEVERITY_ERROR: + case D3D12_MESSAGE_SEVERITY_CORRUPTION: { + msgSeverity = PAL_DEBUG_MESSAGE_SEVERITY_ERROR; + break; + } + } + + s_D3D12.debugCallback(s_D3D12.debugUserData, msgSeverity, msgType, message); + } + queue->lpVtbl->ClearStoredMessages(queue); +} + +PalResult PAL_CALL initGraphicsD3D12( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator) +{ + // load d3d12 + s_D3D12.handle = LoadLibraryA("d3d12.dll"); + s_D3D12.dxgi = LoadLibraryA("dxgi.dll"); + if (!s_D3D12.handle || !s_D3D12.dxgi) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // clang-format off + s_D3D12.createDevice = (PFN_D3D12_CREATE_DEVICE)GetProcAddress( + s_D3D12.handle, + "D3D12CreateDevice"); + + s_D3D12.createDXGIFactory = (PFN_CreateDXGIFactory2)GetProcAddress( + s_D3D12.dxgi, + "CreateDXGIFactory2"); + + s_D3D12.serializeVersionedRootSignature = (PFN_D3D12SerializeVersionedRootSignature)GetProcAddress( + s_D3D12.handle, + "D3D12SerializeVersionedRootSignature"); + + if (debugger && debugger->callback) { + s_D3D12.getDebugInterface = (PFN_D3D12_GET_DEBUG_INTERFACE)GetProcAddress( + s_D3D12.handle, + "D3D12GetDebugInterface"); + + if (s_D3D12.getDebugInterface) { + ID3D12Debug* debug = nullptr; + HRESULT hr = s_D3D12.getDebugInterface(&IID_Debug, (void**)&debug); + if (SUCCEEDED(hr)) { + debug->lpVtbl->EnableDebugLayer(debug); + if (debugger->enableGPUValidation) { + ID3D12Debug1* debug1 = nullptr; + hr = debug->lpVtbl->QueryInterface(debug, &IID_Debug1, (void**)&debug1); + if (SUCCEEDED(hr)) { + debug1->lpVtbl->SetEnableSynchronizedCommandQueueValidation(debug1, TRUE); + debug1->lpVtbl->SetEnableGPUBasedValidation(debug1, TRUE); + debug1->lpVtbl->Release(debug1); + } + } + } + + if (debug) { + // message types + if (!debugger->denyGeneral) + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_APPLICATION_DEFINED; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_MISCELLANEOUS; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_COMPILATION; + + if (!debugger->denyValidation) { + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_INITIALIZATION; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_SHADER; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_RESOURCE_MANIPULATION; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_EXECUTION; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_STATE_GETTING; + s_D3D12.categories[s_D3D12.categoryCount++] = D3D12_MESSAGE_CATEGORY_STATE_SETTING; + } + + // message severities + if (!debugger->denyWarningSeverity) { + s_D3D12.severities[s_D3D12.severityCount++] = D3D12_MESSAGE_SEVERITY_WARNING; + } + + if (!debugger->denyErrorSeverity) { + s_D3D12.severities[s_D3D12.severityCount++] = D3D12_MESSAGE_SEVERITY_ERROR; + s_D3D12.severities[s_D3D12.severityCount++] = D3D12_MESSAGE_SEVERITY_CORRUPTION; + } + + s_D3D12.debugLayer = PAL_TRUE; + s_D3D12.debugCallback = debugger->callback; + } + } + } + // clang-format on + + s_D3D12.factory = nullptr; + s_D3D12.adapters = nullptr; + s_D3D12.adapterCount = 0; + HRESULT result = s_D3D12.createDXGIFactory(0, &IID_Factory, (void**)&s_D3D12.factory); + if (FAILED(result)) { + return makeResultD3D12(result); + } + + s_D3D12.allocator = allocator; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL shutdownGraphicsD3D12() +{ + for (int i = 0; i < s_D3D12.adapterCount; i++) { + s_D3D12.adapters[i].handle->lpVtbl->Release(s_D3D12.adapters[i].handle); + } + + s_D3D12.factory->lpVtbl->Release(s_D3D12.factory); + FreeLibrary(s_D3D12.handle); + FreeLibrary(s_D3D12.dxgi); + + if (s_D3D12.adapters) { + palFree(s_D3D12.allocator, s_D3D12.adapters); + } + memset(&s_D3D12, 0, sizeof(s_D3D12)); +} + +#endif // PAL_HAS_D3D12_BACKEND diff --git a/src/graphics/d3d12/pal_d3d12.h b/src/graphics/d3d12/pal_d3d12.h new file mode 100644 index 00000000..cc37f351 --- /dev/null +++ b/src/graphics/d3d12/pal_d3d12.h @@ -0,0 +1,435 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_D3D12_H +#define _PAL_D3D12_H + +#if PAL_HAS_D3D12_BACKEND +#include "graphics/pal_linear_allocator.h" +#include "pal2/pal_graphics.h" +#include +#include +#include +#include + +#define MAX_RTV 1024 +#define MAX_DSV 512 +#define TEXTURE_PITCH 256 +#define MAX_ATTACHMENTS 8 +#define GRAPHICS_PIPELINE 1220 +#define COMPUTE_PIPELINE 1221 +#define RAY_TRACING_PIPELINE 1222 + +#define DESC_TYPE_RTV 0 +#define DESC_TYPE_DSV 1 +#define DESC_TYPE_SRV 2 +#define DESC_TYPE_UAV 3 + +typedef HRESULT(WINAPI* PFN_CreateDXGIFactory2)( + UINT, + REFIID, + void**); + +typedef HRESULT(__stdcall* PFN_D3D12SerializeVersionedRootSignature)( + const D3D12_VERSIONED_ROOT_SIGNATURE_DESC*, + ID3DBlob**, + ID3DBlob**); + +typedef struct { + uint32_t incrementSize; + uint32_t freeTop; + uint64_t baseOffset; + ID3D12DescriptorHeap* heap; + uint32_t freeList[MAX_RTV]; +} RTVHeapAllocator; + +typedef struct { + uint32_t incrementSize; + uint32_t freeTop; + uint64_t baseOffset; + ID3D12DescriptorHeap* heap; + uint32_t freeList[MAX_DSV]; +} DSVHeapAllocator; + +typedef struct { + uint32_t freeComputeQueues; + uint32_t freeGraphicsQueues; + uint32_t freeCopyQueues; + uint32_t maxVertexLayouts; + uint32_t maxVertexAttributes; + + uint32_t maxAnisotropy; + uint32_t maxPushConstantSize; + uint32_t maxTessellationPatchPoint; + + uint32_t maxDescriptorSampledImages; + uint32_t maxDescriptorStorageImages; + uint32_t maxDescriptorSamplers; + uint32_t maxDescriptorStorageBuffers; + uint32_t maxDescriptorUniformBuffers; + uint32_t maxBoundDescriptorSets; + + uint32_t maxRecursionDepth; + uint32_t maxHitAttributeSize; + uint32_t maxPayloadSize; + uint32_t maxDispatchInvocations; + uint32_t maxDescriptorAccelerationStructures; +} DeviceLimits; + +typedef struct { + uint32_t patchControlPoints; + PalShaderStage stage; + wchar_t entryName[PAL_SHADER_ENTRY_NAME_SIZE]; +} ShaderEntry; + +typedef struct { + PalBool isHitGroup; + PalShaderStage stage; + wchar_t entryName[PAL_SHADER_ENTRY_NAME_SIZE]; +} ShaderExport; + +typedef struct { + wchar_t entryName[PAL_SHADER_ENTRY_NAME_SIZE]; + D3D12_HIT_GROUP_DESC desc; +} RayHitGroup; + +typedef struct { + uint32_t raygenCount; + uint32_t raygenDataSize; + uint32_t missCount; + uint32_t missDataSize; + uint32_t hitCount; + uint32_t hitDataSize; + uint32_t callableCount; + uint32_t callableDataSize; +} ShaderBindingTableInfo; + +typedef struct { + uint32_t incrementSize; + uint32_t nextOffset; + uint64_t cpuBase; + uint64_t gpuBase; + ID3D12DescriptorHeap* handle; +} DescriptorHeap; + +typedef struct { + uint32_t maxUniformBuffers; + uint32_t maxSampledImages; + uint32_t maxStorageBuffers; + uint32_t maxSamplers; + uint32_t maxStorageImages; + uint32_t maxAs; + + uint32_t usedUniformBuffers; + uint32_t usedSampledImages; + uint32_t usedStorageBuffers; + uint32_t usedSamplers; + uint32_t usedStorageImages; + uint32_t usedAs; +} DescriptorHeapLimits; + +typedef struct { + PalDescriptorType type; + D3D12_DESCRIPTOR_RANGE1 range; +} DescriptorSetBinding; + +typedef struct { + uint32_t startIndex; + uint32_t offset; + D3D12_GPU_VIRTUAL_ADDRESS_RANGE_AND_STRIDE region; +} AddressRegion; + +typedef struct { + void* reserved; + D3D_FEATURE_LEVEL level; + ID3D12Device* tmpDevice; + IDXGIAdapter4* handle; +} AdapterD3D12; + +typedef struct { + void* reserved; + void* scratchBuffer; + uint32_t shaderModel; + PalBool canFenceReset; + IDXGIAdapter4* adapter; + ID3D12CommandSignature* meshSignature; + ID3D12CommandSignature* drawIndexedSignature; + ID3D12CommandSignature* drawSignature; + ID3D12CommandSignature* dispatchSignature; + ID3D12CommandSignature* raySignature; + ID3D12InfoQueue* infoQueue; + ID3D12CommandQueue* queue; + ID3D12Device5* handle; + RTVHeapAllocator rtvAllocator; + DSVHeapAllocator dsvAllocator; + DeviceLimits limits; +} DeviceD3D12; + +typedef struct { + void* reserved; + uint32_t fenceValue; + PalQueueType type; + ID3D12Fence* fence; + HANDLE fenceEvent; + ID3D12CommandQueue* handle; +} QueueD3D12; + +typedef struct { + void* reserved; + PalMemoryType type; + ID3D12Heap* handle; +} MemoryD3D12; + +typedef struct { + void* reserved; + HWND handle; +} SurfaceD3D12; + +typedef struct { + void* reserved; + PalBool isMemoryManaged; + DeviceD3D12* device; + ID3D12Resource* handle; + PalImageInfo info; + D3D12_RESOURCE_DESC desc; +} ImageD3D12; + +typedef struct { + void* reserved; + uint32_t heapIndex; + PalImageViewType type; + DXGI_FORMAT format; + ImageD3D12* image; + DeviceD3D12* device; + PalImageSubresourceRange range; +} ImageViewD3D12; + +typedef struct { + void* reserved; + D3D12_SAMPLER_DESC desc; +} SamplerD3D12; + +typedef struct { + void* reserved; + uint32_t imageCount; + uint32_t syncInterval; + uint32_t windowWidth; + uint32_t windowHeight; + uint32_t flags; + DXGI_FORMAT format; + UINT presentFlags; + SurfaceD3D12* surface; + ImageD3D12* images; + ID3D12CommandQueue* queue; + DeviceD3D12* device; + IDXGISwapChain3* handle; +} SwapchainD3D12; + +typedef struct { + void* reserved; + PalBool isTimeline; + PalBool canReset; + UINT64 value; + ID3D12Fence* handle; + HANDLE event; +} FenceD3D12, SemaphoreD3D12; + +typedef struct { + void* reserved; + uint32_t entryCount; + D3D12_SHADER_BYTECODE byteCode; + ShaderEntry* entries; +} ShaderD3D12; + +typedef struct { + void* reserved; + D3D12_COMMAND_LIST_TYPE type; +} CommandPoolD3D12; + +typedef struct { + void* reserved; + PalBool primary; + ID3D12Resource* stagingBuffer; + ID3D12Resource* buffer; + ID3D12CommandAllocator* allocator; + PalLinearAllocator linearAllocator; + void* pipeline; + DeviceD3D12* device; + ID3D12GraphicsCommandList6* handle; +} CommandBufferD3D12; + +typedef struct { + void* reserved; + PalBool isMemoryManaged; + PalBool canStateChange; + PalBufferUsages usages; + uint64_t size; + ID3D12Resource* handle; + DeviceD3D12* device; + D3D12_RESOURCE_DESC desc; +} BufferD3D12; + +typedef struct { + void* reserved; + PalAccelerationStructureType type; + D3D12_GPU_VIRTUAL_ADDRESS address; + ID3D12Resource* handle; +} AccelerationStructureD3D12; + +typedef struct { + void* reserved; + uint32_t constantIndex; + ID3D12RootSignature* handle; +} PipelineLayoutD3D12; + +typedef struct { + void* reserved; + PalBool hasFsr; + uint32_t type; + uint32_t shaderExportCount; + D3D12_SHADING_RATE shadingRate; + D3D_PRIMITIVE_TOPOLOGY topology; + uint32_t* strides; + void* handle; + ShaderExport* shaderExports; + PipelineLayoutD3D12* layout; + ID3D12RootSignature* localRootSignature; + D3D12_SHADING_RATE_COMBINER combinerOps[2]; + ShaderBindingTableInfo sbtInfo; +} PipelineD3D12; + +typedef struct { + void* reserved; + void* stagingPtr; + PalBool isDirty; + uint32_t stagingBufferSize; + uint32_t handleSize; + ID3D12Resource* buffer; + ID3D12Resource* stagingBuffer; + D3D12_GPU_VIRTUAL_ADDRESS baseAddress; + PipelineD3D12* pipeline; + + AddressRegion raygen; + AddressRegion miss; + AddressRegion hit; + AddressRegion callable; +} ShaderBindingTableD3D12; + +typedef struct { + void* reserved; + PalDescriptorIndexingFlags flags; + uint32_t bindingCount; + uint32_t samplerCount; + DescriptorSetBinding* bindings; +} DescriptorSetLayoutD3D12; + +typedef struct { + void* reserved; + uint32_t resourceOffset; + uint32_t samplerOffset; + DescriptorSetLayoutD3D12* layout; + void* pool; // DescriptorPool +} DescriptorSetD3D12; + +typedef struct { + void* reserved; + PalDescriptorIndexingFlags flags; + PalBool hasResourceHeap; + PalBool hasSamplerHeap; + uint32_t maxSets; + uint32_t usedSets; + DescriptorHeapLimits limits; + DescriptorHeap resourceHeap; + DescriptorHeap samplerHeap; + DescriptorSetD3D12* sets; +} DescriptorPoolD3D12; + +typedef struct { + PalBool debugLayer; + uint32_t adapterCount; + uint32_t severityCount; + uint32_t categoryCount; + HMODULE handle; + HMODULE dxgi; + AdapterD3D12* adapters; + IDXGIFactory6* factory; + + PFN_D3D12_CREATE_DEVICE createDevice; + PFN_CreateDXGIFactory2 createDXGIFactory; + PFN_D3D12_GET_DEBUG_INTERFACE getDebugInterface; + PFN_D3D12SerializeVersionedRootSignature serializeVersionedRootSignature; + + PalDebugCallback debugCallback; + void* debugUserData; + const PalAllocator* allocator; + D3D12_MESSAGE_SEVERITY severities[6]; + D3D12_MESSAGE_CATEGORY categories[12]; +} D3D12; + +PalResult makeResultD3D12(HRESULT result); +DXGI_FORMAT formatToD3D12(PalFormat format); +D3D12_COMPARISON_FUNC compareOpToD3D12(PalCompareOp op); + +D3D12_SHADING_RATE_COMBINER combinerOpsToD3D12(PalFragmentShadingRateCombinerOp op); +D3D12_SHADING_RATE shadingRateToD3D12(PalFragmentShadingRate rate); +uint32_t getFormatSizeD3D12(PalFormat format); +DXGI_FORMAT vertexTypeToD3D12(PalVertexType type); + +D3D12_RAYTRACING_INSTANCE_FLAGS instanceFlagsToD3D12(PalAccelerationStructureInstanceFlags flags); +uint32_t samplesToD3D12(PalSampleCount count); + +void fillBuildInfoD3D12( + PalBool getBuildSize, + PalAccelerationStructureBuildInfo* info, + D3D12_RAYTRACING_GEOMETRY_DESC* geometries, + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC* buildInfo); + +void getDescriptorTierLimitsD3D12( + void* device, + PalResourceCapabilities* caps, + PalDescriptorIndexingCapabilities* descCaps); + +void fillSubresourceD3D12( + uint32_t descType, + PalImageViewType type, + const PalImageSubresourceRange* range, + void* desc); + +uint64_t getDescriptorHandleD3D12( + uint32_t index, + uint32_t size, + uint64_t baseOffset); + +void pollMessagesD3D12(DeviceD3D12* device); + +// IIDs +extern IID IID_Device; +extern IID IID_Adapter; +extern IID IID_Factory; +extern IID IID_Debug; +extern IID IID_Debug1; +extern IID IID_InfoQueue; +extern IID IID_Heap; +extern IID IID_Queue; +extern IID IID_Swapchain; +extern IID IID_CommandAllocator; +extern IID IID_CommandList; +extern IID IID_CommandList6; +extern IID IID_CommandSignature; +extern IID IID_DescriptorHeap; +extern IID IID_RootSignature; +extern IID IID_Device5; +extern IID IID_PipelineState; +extern IID IID_StateObject; +extern IID IID_Resource; +extern IID IID_StateObjectProps; +extern IID IID_Fence; + +extern D3D12 s_D3D12; + +#endif // PAL_HAS_D3D12_BACKEND +#endif // _PAL_D3D12_H \ No newline at end of file diff --git a/src/graphics/d3d12/pal_descriptors_d3d12.c b/src/graphics/d3d12/pal_descriptors_d3d12.c new file mode 100644 index 00000000..e96cdf18 --- /dev/null +++ b/src/graphics/d3d12/pal_descriptors_d3d12.c @@ -0,0 +1,603 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalResult PAL_CALL createDescriptorSetLayoutD3D12( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + DescriptorSetLayoutD3D12* layout = nullptr; + DescriptorSetBinding* bindings = nullptr; + uint32_t count = info->bindingCount; + + // partially bound is not supported + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + layout = palAllocate(s_D3D12.allocator, sizeof(DescriptorSetLayoutD3D12), 0); + bindings = palAllocate(s_D3D12.allocator, sizeof(DescriptorSetBinding) * count, 0); + if (!layout || !bindings) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + D3D12_DESCRIPTOR_RANGE_FLAGS rangeFlags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE; + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND) { + rangeFlags = D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE; + } + + uint32_t resourceOffset = 0; + uint32_t samplerOffset = 0; + uint32_t SRVRegister = 0; + uint32_t UAVRegister = 0; + uint32_t CBVRegister = 0; + + uint32_t sampledImageCount = 0; + uint32_t storageImageCount = 0; + uint32_t storageBufferCount = 0; + uint32_t uniformBufferCount = 0; + uint32_t tlasCount = 0; + uint32_t samplerCount = 0; + + for (int i = 0; i < count; i++) { + DescriptorSetBinding* binding = &bindings[i]; + binding->type = info->bindings[i].descriptorType; + + binding->range.NumDescriptors = info->bindings[i].descriptorCount; + binding->range.Flags = rangeFlags; + binding->range.RegisterSpace = 0; + + switch (info->bindings[i].descriptorType) { + case PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + binding->range.BaseShaderRegister = SRVRegister; + + binding->range.OffsetInDescriptorsFromTableStart = resourceOffset; + resourceOffset += info->bindings[i].descriptorCount; + tlasCount += info->bindings[i].descriptorCount; + SRVRegister += info->bindings[i].descriptorCount; + + break; + } + + case PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + binding->range.BaseShaderRegister = SRVRegister; + + binding->range.OffsetInDescriptorsFromTableStart = resourceOffset; + resourceOffset += info->bindings[i].descriptorCount; + sampledImageCount += info->bindings[i].descriptorCount; + SRVRegister += info->bindings[i].descriptorCount; + break; + } + + case PAL_DESCRIPTOR_TYPE_SAMPLER: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + binding->range.BaseShaderRegister = samplerCount; + + binding->range.OffsetInDescriptorsFromTableStart = samplerOffset; + samplerOffset += info->bindings[i].descriptorCount; + samplerCount += info->bindings[i].descriptorCount; + break; + } + + case PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + binding->range.BaseShaderRegister = UAVRegister; + + binding->range.OffsetInDescriptorsFromTableStart = resourceOffset; + resourceOffset += info->bindings[i].descriptorCount; + storageBufferCount += info->bindings[i].descriptorCount; + UAVRegister += info->bindings[i].descriptorCount; + break; + } + + case PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + binding->range.BaseShaderRegister = UAVRegister; + + binding->range.OffsetInDescriptorsFromTableStart = resourceOffset; + resourceOffset += info->bindings[i].descriptorCount; + storageImageCount += info->bindings[i].descriptorCount; + UAVRegister += info->bindings[i].descriptorCount; + break; + } + + case PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER: { + binding->range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + binding->range.BaseShaderRegister = CBVRegister; + + binding->range.OffsetInDescriptorsFromTableStart = resourceOffset; + resourceOffset += info->bindings[i].descriptorCount; + uniformBufferCount += info->bindings[i].descriptorCount; + CBVRegister += info->bindings[i].descriptorCount; + break; + } + } + } + + // check limits + if (sampledImageCount > deviceImpl->limits.maxDescriptorSampledImages) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (storageImageCount > deviceImpl->limits.maxDescriptorStorageImages) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (storageBufferCount > deviceImpl->limits.maxDescriptorStorageBuffers) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (uniformBufferCount > deviceImpl->limits.maxDescriptorUniformBuffers) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (tlasCount > deviceImpl->limits.maxDescriptorAccelerationStructures) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (samplerCount > deviceImpl->limits.maxDescriptorSamplers) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + layout->flags = info->flags; + layout->bindingCount = info->bindingCount; + layout->samplerCount = samplerCount; + layout->bindings = bindings; + *outLayout = (PalDescriptorSetLayout*)layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDescriptorSetLayoutD3D12(PalDescriptorSetLayout* layout) +{ + DescriptorSetLayoutD3D12* layoutImpl = (DescriptorSetLayoutD3D12*)layout; + palFree(s_D3D12.allocator, layoutImpl->bindings); + palFree(s_D3D12.allocator, layoutImpl); +} + +PalResult PAL_CALL createDescriptorPoolD3D12( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + DescriptorPoolD3D12* pool = nullptr; + + // partially bound is not supported + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + pool = palAllocate(s_D3D12.allocator, sizeof(DescriptorPoolD3D12), 0); + if (!pool) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(pool, 0, sizeof(DescriptorPoolD3D12)); + pool->sets = + palAllocate(s_D3D12.allocator, sizeof(DescriptorSetD3D12) * info->maxDescriptorSets, 0); + + if (!pool->sets) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + uint32_t resourceCount = 0; + DescriptorHeapLimits* limits = &pool->limits; + for (int i = 0; i < info->bindingSizeCount; i++) { + PalDescriptorPoolBindingSize* bindingSize = &info->bindingSizes[i]; + if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLER) { + limits->maxSamplers += bindingSize->bindingCount; + + } else if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + limits->maxUniformBuffers += bindingSize->bindingCount; + resourceCount += bindingSize->bindingCount; + + } else if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + limits->maxStorageBuffers += bindingSize->bindingCount; + resourceCount += bindingSize->bindingCount; + + } else if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + limits->maxSampledImages += bindingSize->bindingCount; + resourceCount += bindingSize->bindingCount; + + } else if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + limits->maxStorageImages += bindingSize->bindingCount; + resourceCount += bindingSize->bindingCount; + + } else if (bindingSize->descriptorType == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + limits->maxAs += bindingSize->bindingCount; + resourceCount += bindingSize->bindingCount; + } + } + + // resource heap + if (resourceCount) { + DescriptorHeap* heap = &pool->resourceHeap; + D3D12_CPU_DESCRIPTOR_HANDLE __ret, handle; + D3D12_GPU_DESCRIPTOR_HANDLE __gpuRet, gpuHandle; + + D3D12_DESCRIPTOR_HEAP_DESC desc = {0}; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + desc.NumDescriptors = resourceCount; + + result = deviceImpl->handle->lpVtbl->CreateDescriptorHeap( + deviceImpl->handle, + &desc, + &IID_DescriptorHeap, + (void**)&heap->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // get increment size + heap->incrementSize = deviceImpl->handle->lpVtbl->GetDescriptorHandleIncrementSize( + deviceImpl->handle, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + // get base CPU and GPU base pointer + handle = *heap->handle->lpVtbl->GetCPUDescriptorHandleForHeapStart(heap->handle, &__ret); + heap->cpuBase = handle.ptr; + + gpuHandle = + *heap->handle->lpVtbl->GetGPUDescriptorHandleForHeapStart(heap->handle, &__gpuRet); + heap->gpuBase = gpuHandle.ptr; + + pool->hasResourceHeap = PAL_TRUE; + } + + // sampler heap + if (limits->maxSamplers) { + DescriptorHeap* heap = &pool->samplerHeap; + D3D12_CPU_DESCRIPTOR_HANDLE __ret, handle; + D3D12_GPU_DESCRIPTOR_HANDLE __gpuRet, gpuHandle; + + D3D12_DESCRIPTOR_HEAP_DESC desc = {0}; + desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER; + desc.NumDescriptors = limits->maxSamplers; + + result = deviceImpl->handle->lpVtbl->CreateDescriptorHeap( + deviceImpl->handle, + &desc, + &IID_DescriptorHeap, + (void**)&heap->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // get increment size + heap->incrementSize = deviceImpl->handle->lpVtbl->GetDescriptorHandleIncrementSize( + deviceImpl->handle, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + + // get base CPU and GPU base pointer + handle = *heap->handle->lpVtbl->GetCPUDescriptorHandleForHeapStart(heap->handle, &__ret); + heap->cpuBase = handle.ptr; + + gpuHandle = + *heap->handle->lpVtbl->GetGPUDescriptorHandleForHeapStart(heap->handle, &__gpuRet); + heap->gpuBase = gpuHandle.ptr; + + pool->hasSamplerHeap = PAL_TRUE; + } + + pool->flags = info->flags; + pool->maxSets = info->maxDescriptorSets; + *outPool = (PalDescriptorPool*)pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDescriptorPoolD3D12(PalDescriptorPool* pool) +{ + DescriptorPoolD3D12* poolImpl = (DescriptorPoolD3D12*)pool; + if (poolImpl->hasResourceHeap) { + poolImpl->resourceHeap.handle->lpVtbl->Release(poolImpl->resourceHeap.handle); + } + + if (poolImpl->hasSamplerHeap) { + poolImpl->samplerHeap.handle->lpVtbl->Release(poolImpl->samplerHeap.handle); + } + + palFree(s_D3D12.allocator, poolImpl->sets); + palFree(s_D3D12.allocator, poolImpl); +} + +PalResult PAL_CALL resetDescriptorPoolD3D12(PalDescriptorPool* pool) +{ + DescriptorPoolD3D12* poolImpl = (DescriptorPoolD3D12*)pool; + poolImpl->resourceHeap.nextOffset = 0; + poolImpl->samplerHeap.nextOffset = 0; + poolImpl->usedSets = 0; + + poolImpl->limits.usedAs = 0; + poolImpl->limits.usedSampledImages = 0; + poolImpl->limits.usedSamplers = 0; + poolImpl->limits.usedStorageBuffers = 0; + poolImpl->limits.usedStorageImages = 0; + poolImpl->limits.usedUniformBuffers = 0; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL allocateDescriptorSetD3D12( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + DescriptorPoolD3D12* poolImpl = (DescriptorPoolD3D12*)pool; + DescriptorSetLayoutD3D12* layoutImpl = (DescriptorSetLayoutD3D12*)layout; + DescriptorSetD3D12* set = nullptr; + + uint32_t storageImageCount = 0; + uint32_t samplerCount = 0; + uint32_t storageBufferCount = 0; + uint32_t uniformBufferCount = 0; + uint32_t sampledImageCount = 0; + uint32_t tlasCount = 0; + + // get requirements for the sets using the provided layout + for (int i = 0; i < layoutImpl->bindingCount; i++) { + DescriptorSetBinding* binding = &layoutImpl->bindings[i]; + + if (binding->type == PAL_DESCRIPTOR_TYPE_SAMPLER) { + samplerCount += binding->range.NumDescriptors; + + } else if (binding->type == PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + storageBufferCount += binding->range.NumDescriptors; + + } else if (binding->type == PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + storageImageCount += binding->range.NumDescriptors; + + } else if (binding->type == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + uniformBufferCount += binding->range.NumDescriptors; + + } else if (binding->type == PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + sampledImageCount += binding->range.NumDescriptors; + + } else if (binding->type == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + tlasCount += binding->range.NumDescriptors; + } + } + + // validate descriptor sets limits + if (poolImpl->usedSets + 1 > poolImpl->maxSets) { + // all sets are used + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // check descriptor limits + DescriptorHeapLimits* limits = &poolImpl->limits; + if (limits->usedAs + tlasCount > limits->maxAs) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (limits->usedSampledImages + sampledImageCount > limits->maxSampledImages) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (limits->usedSamplers + samplerCount > limits->maxSamplers) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (limits->usedStorageBuffers + storageBufferCount > limits->maxStorageBuffers) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (limits->usedStorageImages + storageImageCount > limits->maxStorageImages) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (limits->usedUniformBuffers + uniformBufferCount > limits->maxUniformBuffers) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // assign offset base to the set so we know where to start and end for each set. + set = &poolImpl->sets[poolImpl->usedSets++]; + set->resourceOffset = poolImpl->resourceHeap.nextOffset; + set->samplerOffset = poolImpl->samplerHeap.nextOffset; + set->layout = layoutImpl; + set->pool = poolImpl; + + uint32_t totalDescriptors = tlasCount + storageBufferCount + uniformBufferCount; + totalDescriptors += sampledImageCount + storageImageCount; + poolImpl->resourceHeap.nextOffset += totalDescriptors; + poolImpl->samplerHeap.nextOffset += samplerCount; + + limits->usedAs += tlasCount; + limits->usedSampledImages += sampledImageCount; + limits->usedSamplers += samplerCount; + limits->usedStorageBuffers += storageBufferCount; + limits->usedUniformBuffers += uniformBufferCount; + + *outSet = (PalDescriptorSet*)set; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL updateDescriptorSetD3D12( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + for (int i = 0; i < count; i++) { + PalDescriptorSetWriteInfo* info = &infos[i]; + DescriptorSetD3D12* set = (DescriptorSetD3D12*)info->descriptorSet; + DescriptorPoolD3D12* pool = set->pool; + DescriptorSetLayoutD3D12* layout = set->layout; + + if (info->layoutBindingIndex > layout->bindingCount) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + DescriptorSetBinding* binding = &layout->bindings[info->layoutBindingIndex]; + DescriptorHeap* heap = nullptr; + uint32_t index = 0; + uint32_t bindingOffset = binding->range.OffsetInDescriptorsFromTableStart; + + if (binding->type == PAL_DESCRIPTOR_TYPE_SAMPLER) { + heap = &pool->samplerHeap; + index = set->samplerOffset + bindingOffset + info->arrayElement; + + } else { + heap = &pool->resourceHeap; + index = set->resourceOffset + bindingOffset + info->arrayElement; + } + + for (int j = 0; j < info->descriptorCount; j++) { + D3D12_CPU_DESCRIPTOR_HANDLE dst; + dst.ptr = getDescriptorHandleD3D12(index + j, heap->incrementSize, heap->cpuBase); + + if (info->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLER) { + if (info->samplerInfos) { + SamplerD3D12* sampler = (SamplerD3D12*)info->samplerInfos[j].sampler; + deviceImpl->handle->lpVtbl->CreateSampler( + deviceImpl->handle, + &sampler->desc, + dst); + + } else { + D3D12_SAMPLER_DESC desc = {0}; + desc.MaxAnisotropy = 1; + desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + desc.BorderColor[3] = 1.0f; + + desc.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + desc.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + desc.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + desc.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT; + deviceImpl->handle->lpVtbl->CreateSampler(deviceImpl->handle, &desc, dst); + } + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + D3D12_SHADER_RESOURCE_VIEW_DESC desc = {0}; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE; + + if (info->tlasInfos) { + AccelerationStructureD3D12* tlas = nullptr; + tlas = (AccelerationStructureD3D12*)info->tlasInfos[j].tlas; + desc.RaytracingAccelerationStructure.Location = tlas->address; + } + + deviceImpl->handle->lpVtbl + ->CreateShaderResourceView(deviceImpl->handle, nullptr, &desc, dst); + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + D3D12_SHADER_RESOURCE_VIEW_DESC desc = {0}; + desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + + PalImageSubresourceRange range = {0}; + PalImageViewType type; + ID3D12Resource* handle = nullptr; + + if (info->imageViewInfos) { + ImageViewD3D12* imageView = (ImageViewD3D12*)info->imageViewInfos[j].imageView; + desc.Format = imageView->format; + type = imageView->type; + range = imageView->range; + handle = imageView->image->handle; + + } else { + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + type = PAL_IMAGE_VIEW_TYPE_2D; + range.mipLevelCount = 1; + range.layerArrayCount = 1; + range.aspect = PAL_IMAGE_ASPECT_COLOR; + } + + fillSubresourceD3D12(DESC_TYPE_SRV, type, &range, &desc); + deviceImpl->handle->lpVtbl + ->CreateShaderResourceView(deviceImpl->handle, handle, &desc, dst); + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {0}; + PalImageSubresourceRange range = {0}; + PalImageViewType type; + ID3D12Resource* handle = nullptr; + + if (info->imageViewInfos) { + ImageViewD3D12* imageView = (ImageViewD3D12*)info->imageViewInfos[j].imageView; + desc.Format = imageView->format; + type = imageView->type; + range = imageView->range; + handle = imageView->image->handle; + + } else { + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + type = PAL_IMAGE_VIEW_TYPE_2D; + range.mipLevelCount = 1; + range.layerArrayCount = 1; + range.aspect = PAL_IMAGE_ASPECT_COLOR; + } + + fillSubresourceD3D12(DESC_TYPE_UAV, type, &range, &desc); + deviceImpl->handle->lpVtbl + ->CreateUnorderedAccessView(deviceImpl->handle, handle, nullptr, &desc, dst); + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + D3D12_CONSTANT_BUFFER_VIEW_DESC desc = {0}; + D3D12_GPU_VIRTUAL_ADDRESS address = 0; + + if (info->bufferInfos) { + PalDescriptorBufferInfo* bufferInfo = &info->bufferInfos[j]; + BufferD3D12* buffer = (BufferD3D12*)bufferInfo->buffer; + desc.SizeInBytes = (UINT)bufferInfo->size; + address = buffer->handle->lpVtbl->GetGPUVirtualAddress(buffer->handle); + desc.BufferLocation = address + bufferInfo->offset; + } + + deviceImpl->handle->lpVtbl->CreateConstantBufferView( + deviceImpl->handle, + &desc, + dst); + + } else { + // storage buffer + D3D12_UNORDERED_ACCESS_VIEW_DESC desc = {0}; + desc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + + ID3D12Resource* handle = nullptr; + if (info->bufferInfos) { + PalDescriptorBufferInfo* bufferInfo = &info->bufferInfos[j]; + BufferD3D12* buffer = (BufferD3D12*)bufferInfo->buffer; + + uint32_t stride = 4; + if (bufferInfo->stride) { + stride = (UINT)bufferInfo->stride; + desc.Buffer.StructureByteStride = stride; + } else { + desc.Format = DXGI_FORMAT_R32_TYPELESS; + desc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; + } + + handle = buffer->handle; + desc.Buffer.FirstElement = (UINT)bufferInfo->offset / stride; + desc.Buffer.NumElements = (UINT)bufferInfo->size / stride; + } + + deviceImpl->handle->lpVtbl + ->CreateUnorderedAccessView(deviceImpl->handle, handle, nullptr, &desc, dst); + } + } + } + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_D3D12_BACKEND diff --git a/src/graphics/d3d12/pal_device_d3d12.c b/src/graphics/d3d12/pal_device_d3d12.c new file mode 100644 index 00000000..16d916e0 --- /dev/null +++ b/src/graphics/d3d12/pal_device_d3d12.c @@ -0,0 +1,672 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalAdapterFeatures PAL_CALL getAdapterFeaturesD3D12(PalAdapter*); +uint32_t PAL_CALL getHighestSupportedShaderTargetD3D12( + PalAdapter*, + PalShaderFormats); + +static void convertToWcharD3D12( + const char* src, + wchar_t dst[PAL_SHADER_ENTRY_NAME_SIZE]) +{ + int i = 0; + for (; i < PAL_SHADER_ENTRY_NAME_SIZE - 1 && src[i]; i++) { + dst[i] = (wchar_t)src[i]; + } + dst[i] = L'\0'; +} + +PalResult PAL_CALL createDeviceD3D12( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice) +{ + HRESULT result; + DeviceD3D12* device = nullptr; + AdapterD3D12* adapterImpl = (AdapterD3D12*)adapter; + + // check if any of the features are not supported + PalAdapterFeatures adapterFeatures = getAdapterFeaturesD3D12(adapter); + PalBool valid = (adapterFeatures & features) == features; + if (!valid) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + device = palAllocate(s_D3D12.allocator, sizeof(DeviceD3D12), 0); + if (!device) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + ID3D12Device* tmpDevice = nullptr; + memset(device, 0, sizeof(DeviceD3D12)); + DeviceLimits* limits = &device->limits; + + // get and cache highest shader model + device->shaderModel = getHighestSupportedShaderTargetD3D12(adapter, PAL_SHADER_FORMAT_DXIL); + result = s_D3D12.createDevice( + (IUnknown*)adapterImpl->handle, + adapterImpl->level, + &IID_Device, + (void**)&tmpDevice); + + if (FAILED(result)) { + palFree(s_D3D12.allocator, device); + return makeResultD3D12(result); + } + + result = tmpDevice->lpVtbl->QueryInterface(tmpDevice, &IID_Device5, (void**)&device->handle); + + tmpDevice->lpVtbl->Release(tmpDevice); + if (s_D3D12.debugLayer) { + result = device->handle->lpVtbl->QueryInterface( + device->handle, + &IID_InfoQueue, + (void**)&device->infoQueue); + + if (SUCCEEDED(result)) { + // allocate a scratch buffer for messages. 16KB + device->scratchBuffer = palAllocate(s_D3D12.allocator, 16384, 0); + if (!device->scratchBuffer) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + D3D12_MESSAGE_ID denyIDs[] = { + D3D12_MESSAGE_ID_MAP_INVALID_NULLRANGE, + D3D12_MESSAGE_ID_LIVE_OBJECT_SUMMARY}; + + device->infoQueue->lpVtbl->SetBreakOnSeverity( + device->infoQueue, + D3D12_MESSAGE_SEVERITY_ERROR, + TRUE); + + device->infoQueue->lpVtbl->SetBreakOnSeverity( + device->infoQueue, + D3D12_MESSAGE_SEVERITY_CORRUPTION, + TRUE); + + D3D12_INFO_QUEUE_FILTER filter = {0}; + filter.AllowList.NumSeverities = s_D3D12.severityCount; + filter.AllowList.pSeverityList = s_D3D12.severities; + filter.AllowList.NumCategories = s_D3D12.categoryCount; + filter.AllowList.pCategoryList = s_D3D12.categories; + filter.DenyList.NumIDs = sizeof(denyIDs) / sizeof(D3D12_MESSAGE_ID); + filter.DenyList.pIDList = denyIDs; + device->infoQueue->lpVtbl->PushStorageFilter(device->infoQueue, &filter); + } + } + + // create a temporary graphics queue + D3D12_COMMAND_QUEUE_DESC desc = {0}; + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + + result = device->handle->lpVtbl + ->CreateCommandQueue(device->handle, &desc, &IID_Queue, (void**)&device->queue); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + // create command signatures + D3D12_INDIRECT_ARGUMENT_DESC argumentDesc = {0}; + D3D12_COMMAND_SIGNATURE_DESC signatureDesc = {0}; + signatureDesc.NumArgumentDescs = 1; + signatureDesc.pArgumentDescs = &argumentDesc; + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH) { + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_MESH; + signatureDesc.ByteStride = sizeof(D3D12_DISPATCH_MESH_ARGUMENTS); + + result = device->handle->lpVtbl->CreateCommandSignature( + device->handle, + &signatureDesc, + nullptr, + &IID_CommandSignature, + (void**)&device->meshSignature); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_RAYS; + signatureDesc.ByteStride = sizeof(D3D12_DISPATCH_RAYS_DESC); + + result = device->handle->lpVtbl->CreateCommandSignature( + device->handle, + &signatureDesc, + nullptr, + &IID_CommandSignature, + (void**)&device->raySignature); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW) { + // draw indexed + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + signatureDesc.ByteStride = sizeof(D3D12_DRAW_INDEXED_ARGUMENTS); + + result = device->handle->lpVtbl->CreateCommandSignature( + device->handle, + &signatureDesc, + nullptr, + &IID_CommandSignature, + (void**)&device->drawIndexedSignature); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + // draw + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; + signatureDesc.ByteStride = sizeof(D3D12_DRAW_ARGUMENTS); + + result = device->handle->lpVtbl->CreateCommandSignature( + device->handle, + &signatureDesc, + nullptr, + &IID_CommandSignature, + (void**)&device->drawSignature); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DISPATCH) { + argumentDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + signatureDesc.ByteStride = sizeof(D3D12_DISPATCH_ARGUMENTS); + + result = device->handle->lpVtbl->CreateCommandSignature( + device->handle, + &signatureDesc, + nullptr, + &IID_CommandSignature, + (void**)&device->dispatchSignature); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + // create an internal heap for RTV and DSV + D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {0}; + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; + heapDesc.NumDescriptors = MAX_RTV; + + result = device->handle->lpVtbl->CreateDescriptorHeap( + device->handle, + &heapDesc, + &IID_DescriptorHeap, + (void**)&device->rtvAllocator.heap); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV; + heapDesc.NumDescriptors = MAX_DSV; + result = device->handle->lpVtbl->CreateDescriptorHeap( + device->handle, + &heapDesc, + &IID_DescriptorHeap, + (void**)&device->dsvAllocator.heap); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + for (int i = 0; i < MAX_RTV; i++) { + device->rtvAllocator.freeList[i] = i + 1; + } + + for (int i = 0; i < MAX_DSV; i++) { + device->dsvAllocator.freeList[i] = i + 1; + } + + device->rtvAllocator.freeTop = 0; + device->rtvAllocator.incrementSize = device->handle->lpVtbl->GetDescriptorHandleIncrementSize( + device->handle, + D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + + device->dsvAllocator.freeTop = 0; + device->dsvAllocator.incrementSize = device->handle->lpVtbl->GetDescriptorHandleIncrementSize( + device->handle, + D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + + // get base CPU pointer + D3D12_CPU_DESCRIPTOR_HANDLE __ret, dst; + dst = *device->rtvAllocator.heap->lpVtbl->GetCPUDescriptorHandleForHeapStart( + device->rtvAllocator.heap, + &__ret); + device->rtvAllocator.baseOffset = dst.ptr; + + dst = *device->dsvAllocator.heap->lpVtbl->GetCPUDescriptorHandleForHeapStart( + device->dsvAllocator.heap, + &__ret); + device->dsvAllocator.baseOffset = dst.ptr; + + // API enforced limits + limits->freeComputeQueues = 2; + limits->freeGraphicsQueues = 2; + limits->freeCopyQueues = 2; + limits->maxVertexLayouts = 30; + limits->maxVertexAttributes = 30; + + limits->maxAnisotropy = 16; + limits->maxPushConstantSize = 256; + limits->maxTessellationPatchPoint = 32; + limits->maxBoundDescriptorSets = 30; + + if (features & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING) { + limits->maxDescriptorSampledImages = 4096; + limits->maxDescriptorStorageImages = 1024; + limits->maxDescriptorSamplers = 512; + limits->maxDescriptorStorageBuffers = 2048; + limits->maxDescriptorUniformBuffers = 256; + + } else { + limits->maxDescriptorSampledImages = 1024; + limits->maxDescriptorStorageImages = 512; + limits->maxDescriptorSamplers = 256; + limits->maxDescriptorStorageBuffers = 512; + limits->maxDescriptorUniformBuffers = 256; + limits->maxBoundDescriptorSets = 30; + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + limits->maxRecursionDepth = 31; + limits->maxHitAttributeSize = 32; + limits->maxPayloadSize = 64; + limits->maxDispatchInvocations = 16000000; + limits->maxDescriptorAccelerationStructures = 4; + } + + if (features & PAL_ADAPTER_FEATURE_FENCE_RESET) { + device->canFenceReset = PAL_TRUE; + } + + device->adapter = adapterImpl->handle; + *outDevice = (PalDevice*)device; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDeviceD3D12(PalDevice* device) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + if (deviceImpl->meshSignature) { + deviceImpl->meshSignature->lpVtbl->Release(deviceImpl->meshSignature); + } + + if (deviceImpl->raySignature) { + deviceImpl->raySignature->lpVtbl->Release(deviceImpl->raySignature); + } + + if (deviceImpl->dispatchSignature) { + deviceImpl->dispatchSignature->lpVtbl->Release(deviceImpl->dispatchSignature); + } + + if (deviceImpl->drawIndexedSignature) { + deviceImpl->drawIndexedSignature->lpVtbl->Release(deviceImpl->drawIndexedSignature); + } + + if (deviceImpl->drawSignature) { + deviceImpl->drawSignature->lpVtbl->Release(deviceImpl->drawSignature); + } + + deviceImpl->rtvAllocator.heap->lpVtbl->Release(deviceImpl->rtvAllocator.heap); + deviceImpl->dsvAllocator.heap->lpVtbl->Release(deviceImpl->dsvAllocator.heap); + + deviceImpl->queue->lpVtbl->Release(deviceImpl->queue); + deviceImpl->handle->lpVtbl->Release(deviceImpl->handle); + + if (deviceImpl->infoQueue) { + deviceImpl->infoQueue->lpVtbl->Release(deviceImpl->infoQueue); + palFree(s_D3D12.allocator, deviceImpl->scratchBuffer); + } + + palFree(s_D3D12.allocator, deviceImpl); +} + +uint32_t PAL_CALL getDeviceLostReasonD3D12(PalDevice* device) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + return deviceImpl->handle->lpVtbl->GetDeviceRemovedReason(deviceImpl->handle); +} + +PalResult PAL_CALL allocateMemoryD3D12( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + MemoryD3D12* memory = nullptr; + + memory = palAllocate(s_D3D12.allocator, sizeof(MemoryD3D12), 0); + if (!memory) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + D3D12_HEAP_DESC desc = {0}; + desc.SizeInBytes = size; + desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT; + if (type == PAL_MEMORY_TYPE_CPU_READBACK) { + desc.Properties.Type = D3D12_HEAP_TYPE_READBACK; + + } else if (type == PAL_MEMORY_TYPE_CPU_UPLOAD) { + desc.Properties.Type = D3D12_HEAP_TYPE_UPLOAD; + } + + result = deviceImpl->handle->lpVtbl + ->CreateHeap(deviceImpl->handle, &desc, &IID_Heap, (void**)&memory->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + memory->type = type; + *outMemory = (PalMemory*)memory; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL freeMemoryD3D12(PalMemory* memory) +{ + MemoryD3D12* memoryImpl = (MemoryD3D12*)memory; + memoryImpl->handle->lpVtbl->Release(memoryImpl->handle); + palFree(s_D3D12.allocator, memoryImpl); +} + +void PAL_CALL querySamplerAnisotropyCapabilitiesD3D12( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps) +{ + caps->maxAnisotropy = 16; // default on most d3d12 hardwares +} + +void PAL_CALL queryMultiViewCapabilitiesD3D12( + PalDevice* device, + PalMultiViewCapabilities* caps) +{ + caps->maxViewCount = D3D12_MAX_VIEW_INSTANCE_COUNT; +} + +void PAL_CALL queryMultiViewportCapabilitiesD3D12( + PalDevice* device, + PalMultiViewportCapabilities* caps) +{ + caps->maxCount = D3D12_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; +} + +void PAL_CALL queryDepthStencilCapabilitiesD3D12( + PalDevice* device, + PalDepthStencilCapabilities* caps) +{ + // depth resolve modes + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_AVERAGE); + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_MIN); + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_MAX); + + // stencil resolve modes + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_MIN); + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_MAX); + + caps->supportsIndependentResolve = PAL_FALSE; + caps->supportsIndependentResolveNone = PAL_FALSE; +} + +void PAL_CALL queryFragmentShadingRateCapabilitiesD3D12( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + caps->supportedShadingRates = (1u << PAL_FRAGMENT_SHADING_RATE_1X1); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_1X2); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_2X1); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_2X2); + + D3D12_FEATURE_DATA_D3D12_OPTIONS6 options = {0}; + deviceImpl->handle->lpVtbl->CheckFeatureSupport( + deviceImpl->handle, + D3D12_FEATURE_D3D12_OPTIONS6, + &options, + sizeof(options)); + + if (options.AdditionalShadingRatesSupported) { + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_2X4); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_4X2); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_4X4); + } + + // there are supported if fragment shading rate feature is + caps->supportedCombinerOps = 0; + caps->supportedCombinerOps |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP); + caps->supportedCombinerOps |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE); + caps->supportedCombinerOps |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN); + caps->supportedCombinerOps |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX); + caps->supportedCombinerOps |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL); + + caps->minTexelWidth = 1; // safe default + caps->minTexelHeight = 1; // safe default + caps->maxTexelWidth = options.ShadingRateImageTileSize; + caps->maxTexelHeight = options.ShadingRateImageTileSize; +} + +void PAL_CALL queryMeshShaderCapabilitiesD3D12( + PalDevice* device, + PalMeshShaderCapabilities* caps) +{ + // these are not exposed by d3d12. We use the offical mesh shader spec + caps->maxOutputPrimitives = 256; + caps->maxOutputVertices = 256; + caps->maxWorkGroupInvocations = 128; + caps->maxTaskWorkGroupInvocations = 128; + + caps->maxWorkGroupCount[0] = 65535; + caps->maxWorkGroupCount[1] = 65535; + caps->maxWorkGroupCount[2] = 65535; + + caps->maxTaskWorkGroupCount[0] = 65535; + caps->maxTaskWorkGroupCount[1] = 65535; + caps->maxTaskWorkGroupCount[2] = 65535; +} + +void PAL_CALL queryRayTracingCapabilitiesD3D12( + PalDevice* device, + PalRayTracingCapabilities* caps) +{ + // these are safe defaults. D3d12 does not expose them. + caps->maxRecursionDepth = 31; + caps->maxHitAttributeSize = 32; + caps->maxInstanceCount = 1000000; + caps->maxPrimitiveCount = 10000000; + caps->maxGeometryCount = 100000; + caps->maxPayloadSize = 64; + caps->maxDispatchInvocations = 16000000; +} + +void PAL_CALL queryDescriptorIndexingCapabilitiesD3D12( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + caps->flags = PAL_DESCRIPTOR_INDEXING_FLAG_NON_UNIFORM_INDEXING; + caps->flags |= PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND; + getDescriptorTierLimitsD3D12(deviceImpl->handle, nullptr, caps); +} + +PalResult PAL_CALL createQueueD3D12( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + QueueD3D12* queue = nullptr; + D3D12_COMMAND_QUEUE_DESC desc = {0}; + + switch (type) { + case PAL_QUEUE_TYPE_COMPUTE: { + desc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE; + if (!deviceImpl->limits.freeComputeQueues) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + deviceImpl->limits.freeComputeQueues--; + break; + } + + case PAL_QUEUE_TYPE_GRAPHICS: { + desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; + if (!deviceImpl->limits.freeGraphicsQueues) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + deviceImpl->limits.freeGraphicsQueues--; + break; + } + + case PAL_QUEUE_TYPE_COPY: { + desc.Type = D3D12_COMMAND_LIST_TYPE_COPY; + if (!deviceImpl->limits.freeCopyQueues) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + deviceImpl->limits.freeCopyQueues--; + break; + } + } + + queue = palAllocate(s_D3D12.allocator, sizeof(QueueD3D12), 0); + if (!queue) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + result = deviceImpl->handle->lpVtbl->CreateCommandQueue( + deviceImpl->handle, + &desc, + &IID_Queue, + (void**)&queue->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + palFree(s_D3D12.allocator, queue); + return makeResultD3D12(result); + } + + // create fence used for queue wait + result = deviceImpl->handle->lpVtbl + ->CreateFence(deviceImpl->handle, 0, 0, &IID_Fence, (void**)&queue->fence); + + if (FAILED(result)) { + palFree(s_D3D12.allocator, queue); + return makeResultD3D12(result); + } + + // create event + queue->fenceEvent = CreateEvent(nullptr, PAL_FALSE, PAL_FALSE, nullptr); + if (!queue->fenceEvent) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + queue->fenceValue = 0; + queue->type = type; + *outQueue = (PalQueue*)queue; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyQueueD3D12(PalQueue* queue) +{ + QueueD3D12* queueImpl = (QueueD3D12*)queue; + queueImpl->fence->lpVtbl->Release(queueImpl->fence); + queueImpl->handle->lpVtbl->Release(queueImpl->handle); + CloseHandle(queueImpl->fenceEvent); + palFree(s_D3D12.allocator, queueImpl); +} + +PalResult PAL_CALL waitQueueD3D12(PalQueue* queue) +{ + QueueD3D12* queueImpl = (QueueD3D12*)queue; + ID3D12Fence* fence = queueImpl->fence; + HANDLE event = queueImpl->fenceEvent; + + // wait on the fence if the submited work is not done + if (fence->lpVtbl->GetCompletedValue(fence) < queueImpl->fenceValue) { + fence->lpVtbl->SetEventOnCompletion(fence, queueImpl->fenceValue, event); + WaitForSingleObject(event, INFINITE); + CloseHandle(event); + } + return PAL_RESULT_SUCCESS; +} + +PalBool PAL_CALL canQueuePresentD3D12( + PalQueue* queue, + PalSurface* surface) +{ + QueueD3D12* queueImpl = (QueueD3D12*)queue; + if (queueImpl->type == PAL_QUEUE_TYPE_GRAPHICS) { + return PAL_TRUE; // all graphics queues support presentation + } + return PAL_FALSE; +} + +PalResult PAL_CALL createShaderD3D12( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader) +{ + ShaderD3D12* shader = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + void* bytecode = nullptr; + + shader = palAllocate(s_D3D12.allocator, sizeof(ShaderD3D12), 0); + bytecode = palAllocate(s_D3D12.allocator, info->codeSize, 0); + if (!shader || !bytecode) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // allocate entries array + shader->entries = palAllocate(s_D3D12.allocator, sizeof(ShaderEntry) * info->entryCount, 0); + if (!shader->entries) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + for (int i = 0; i < info->entryCount; i++) { + ShaderEntry* entry = &shader->entries[i]; + convertToWcharD3D12(info->entries[i].entryName, entry->entryName); + entry->patchControlPoints = info->entries[i].patchControlPoints; + entry->stage = info->entries[i].stage; + } + + memcpy(bytecode, info->code, info->codeSize); + shader->byteCode.pShaderBytecode = bytecode; + shader->byteCode.BytecodeLength = info->codeSize; + + shader->entryCount = info->entryCount; + *outShader = (PalShader*)shader; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyShaderD3D12(PalShader* shader) +{ + ShaderD3D12* shaderImpl = (ShaderD3D12*)shader; + palFree(s_D3D12.allocator, (void*)shaderImpl->byteCode.pShaderBytecode); + palFree(s_D3D12.allocator, shaderImpl->entries); + palFree(s_D3D12.allocator, shaderImpl); +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_image_d3d12.c b/src/graphics/d3d12/pal_image_d3d12.c new file mode 100644 index 00000000..bfc9b1e5 --- /dev/null +++ b/src/graphics/d3d12/pal_image_d3d12.c @@ -0,0 +1,407 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +static D3D12_FILTER filterToD3D12( + PalFilterMode minFilter, + PalFilterMode magFilter, + PalSamplerMipmapMode mode) +{ + // all the enums start with min so we start with min filter + switch (minFilter) { + case PAL_FILTER_MODE_NEAREST: { + switch (magFilter) { + case PAL_FILTER_MODE_NEAREST: { + // min and mag are nearest. Check sampler mipmap mode + if (mode == PAL_SAMPLER_MIPMAP_MODE_NEAREST) { + return D3D12_FILTER_MIN_MAG_MIP_POINT; // sampler mode nearest + } else { + return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR; // sampler mode linear + } + } + + case PAL_FILTER_MODE_LINEAR: { + // min is nearest, mag is linear . Check sampler mipmap mode + if (mode == PAL_SAMPLER_MIPMAP_MODE_NEAREST) { + return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT; // sampler mode nearest + } else { + return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR; // sampler mode linear + } + } + } + } + + case PAL_FILTER_MODE_LINEAR: { + switch (magFilter) { + case PAL_FILTER_MODE_NEAREST: { + // min is linear, mag is nearest. Check sampler mipmap mode + if (mode == PAL_SAMPLER_MIPMAP_MODE_NEAREST) { + return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT; // sampler mode nearest + } else { + return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; // sampler mode linear + } + } + + case PAL_FILTER_MODE_LINEAR: { + // min and mag are linear. Check sampler mipmap mode + if (mode == PAL_SAMPLER_MIPMAP_MODE_NEAREST) { + return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; // sampler mode nearest + } else { + return D3D12_FILTER_MIN_MAG_MIP_LINEAR; // sampler mode linear + } + } + } + } + } + + return D3D12_FILTER_MIN_MAG_MIP_POINT; +} + +static D3D12_TEXTURE_ADDRESS_MODE addressModeToD3D12(PalSamplerAddressMode mode) +{ + switch (mode) { + case PAL_SAMPLER_ADDRESS_MODE_REPEAT: { + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; + } + + case PAL_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: { + return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; + } + case PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: { + return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + } + case PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: { + return D3D12_TEXTURE_ADDRESS_MODE_BORDER; + } + } + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; +} + +static void borderColorToD3D12( + PalBorderColor color, + float outColor[4]) +{ + switch (color) { + case PAL_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK: + case PAL_BORDER_COLOR_INT_TRANSPARENT_BLACK: { + outColor[0] = 0.0f; + outColor[1] = 0.0f; + outColor[2] = 0.0f; + outColor[3] = 0.0f; + break; + } + + case PAL_BORDER_COLOR_FLOAT_OPAQUE_BLACK: + case PAL_BORDER_COLOR_INT_OPAQUE_BLACK: { + outColor[0] = 0.0f; + outColor[1] = 0.0f; + outColor[2] = 0.0f; + outColor[3] = 1.0f; + break; + } + + case PAL_BORDER_COLOR_FLOAT_OPAQUE_WHITE: + case PAL_BORDER_COLOR_INT_OPAQUE_WHITE: { + outColor[0] = 1.0f; + outColor[1] = 1.0f; + outColor[2] = 1.0f; + outColor[3] = 1.0f; + break; + } + } + + outColor[0] = 0.0f; + outColor[1] = 0.0f; + outColor[2] = 0.0f; + outColor[3] = 0.0f; +} + +PalResult PAL_CALL createImageD3D12( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage) +{ + HRESULT result; + ImageD3D12* image = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + + image = palAllocate(s_D3D12.allocator, sizeof(ImageD3D12), 0); + if (!image) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(image, 0, sizeof(ImageD3D12)); + image->desc.Width = (UINT64)info->width; + image->desc.Height = (UINT64)info->height; + image->desc.DepthOrArraySize = (UINT16)info->arrayLayerCount; + image->desc.MipLevels = (UINT16)info->mipLevelCount; + image->desc.Format = formatToD3D12(info->format); + image->desc.SampleDesc.Count = samplesToD3D12(info->sampleCount); + image->desc.SampleDesc.Quality = 0; + + image->desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + if (info->type == PAL_IMAGE_TYPE_3D) { + image->desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + image->desc.DepthOrArraySize = (UINT16)info->depth; + + } else if (info->type == PAL_IMAGE_TYPE_1D) { + image->desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE1D; + } + + if (info->usages & PAL_IMAGE_USAGE_COLOR_ATTACHEMENT) { + image->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + } + + if (info->usages & PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT) { + image->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + } + + if (info->usages & PAL_IMAGE_USAGE_STORAGE) { + image->desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + } + + image->isMemoryManaged = PAL_FALSE; + if (info->memoryUsage != PAL_IMAGE_MEMORY_USAGE_MANUAL) { + D3D12_HEAP_PROPERTIES heapProps = {0}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &image->desc, + D3D12_RESOURCE_STATE_COMMON, + nullptr, + &IID_Resource, + (void**)&image->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + image->isMemoryManaged = PAL_TRUE; + } + + image->info.depth = info->depth; + image->info.arrayLayerCount = info->arrayLayerCount; + image->info.belongsToSwapchain = PAL_FALSE; + image->info.type = info->type; + image->info.format = info->format; + image->info.usages = info->usages; + image->info.height = info->height; + image->info.mipLevelCount = info->mipLevelCount; + image->info.sampleCount = info->sampleCount; + image->info.width = info->width; + + image->device = deviceImpl; + *outImage = (PalImage*)image; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyImageD3D12(PalImage* image) +{ + ImageD3D12* imageImpl = (ImageD3D12*)image; + if (imageImpl->isMemoryManaged) { + imageImpl->handle->lpVtbl->Release(imageImpl->handle); + } + palFree(s_D3D12.allocator, imageImpl); +} + +void PAL_CALL getImageInfoD3D12( + PalImage* image, + PalImageInfo* info) +{ + ImageD3D12* imageImpl = (ImageD3D12*)image; + *info = imageImpl->info; +} + +void PAL_CALL getImageMemoryRequirementsD3D12( + PalImage* image, + PalMemoryRequirements* requirements) +{ + ImageD3D12* imageImpl = (ImageD3D12*)image; + ID3D12Device5* device = imageImpl->device->handle; + + D3D12_RESOURCE_ALLOCATION_INFO allocationInfo = {0}; + D3D12_RESOURCE_ALLOCATION_INFO __ret = {0}; + allocationInfo = + *device->lpVtbl->GetResourceAllocationInfo(device, &__ret, 0, 1, &imageImpl->desc); + + requirements->supportedMemoryTypes = (1u << PAL_MEMORY_TYPE_GPU_ONLY); + requirements->alignment = allocationInfo.Alignment; + requirements->size = allocationInfo.SizeInBytes; +} + +PalResult PAL_CALL bindImageMemoryD3D12( + PalImage* image, + PalMemory* memory, + uint64_t offset) +{ + HRESULT result; + ImageD3D12* imageImpl = (ImageD3D12*)image; + ID3D12Device5* device = imageImpl->device->handle; + if (imageImpl->info.belongsToSwapchain) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + if (imageImpl->isMemoryManaged) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + ID3D12Heap* mem = (ID3D12Heap*)memory; + result = device->lpVtbl->CreatePlacedResource( + device, + mem, + offset, + &imageImpl->desc, + 0, + nullptr, + &IID_Resource, + (void**)&imageImpl->handle); + + if (FAILED(result)) { + pollMessagesD3D12(imageImpl->device); + return makeResultD3D12(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createImageViewD3D12( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView) +{ + HRESULT result; + ImageViewD3D12* imageView = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + ImageD3D12* imageImpl = (ImageD3D12*)image; + + imageView = palAllocate(s_D3D12.allocator, sizeof(ImageViewD3D12), 0); + if (!imageView) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + imageView->heapIndex = UINT32_MAX; + PalBool hasRTV = (imageImpl->desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); + PalBool hasDSV = (imageImpl->desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL); + + imageView->format = formatToD3D12(info->format); + if (info->subresourceRange.aspect == PAL_IMAGE_ASPECT_COLOR && hasRTV) { + RTVHeapAllocator* allocator = &deviceImpl->rtvAllocator; + uint32_t index = allocator->freeTop; + allocator->freeTop = allocator->freeList[index]; + + D3D12_CPU_DESCRIPTOR_HANDLE dst; + dst.ptr = getDescriptorHandleD3D12(index, allocator->incrementSize, allocator->baseOffset); + + D3D12_RENDER_TARGET_VIEW_DESC desc = {0}; + desc.Format = imageView->format; + fillSubresourceD3D12(DESC_TYPE_RTV, info->type, &info->subresourceRange, &desc); + imageView->heapIndex = index; + deviceImpl->handle->lpVtbl + ->CreateRenderTargetView(deviceImpl->handle, imageImpl->handle, &desc, dst); + + } else if (info->subresourceRange.aspect != PAL_IMAGE_ASPECT_COLOR && hasDSV) { + DSVHeapAllocator* allocator = &deviceImpl->dsvAllocator; + uint32_t index = allocator->freeTop; + allocator->freeTop = allocator->freeList[index]; + + D3D12_CPU_DESCRIPTOR_HANDLE dst; + dst.ptr = getDescriptorHandleD3D12(index, allocator->incrementSize, allocator->baseOffset); + + D3D12_DEPTH_STENCIL_VIEW_DESC desc = {0}; + desc.Format = imageView->format; + fillSubresourceD3D12(DESC_TYPE_DSV, info->type, &info->subresourceRange, &desc); + imageView->heapIndex = index; + deviceImpl->handle->lpVtbl + ->CreateDepthStencilView(deviceImpl->handle, imageImpl->handle, &desc, dst); + } + + imageView->range = info->subresourceRange; + imageView->type = info->type; + imageView->image = imageImpl; + + imageView->device = deviceImpl; + *outImageView = (PalImageView*)imageView; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyImageViewD3D12(PalImageView* imageView) +{ + ImageViewD3D12* imageViewImpl = (ImageViewD3D12*)imageView; + DeviceD3D12* device = imageViewImpl->device; + RTVHeapAllocator* allocator = nullptr; + + if (imageViewImpl->heapIndex != UINT32_MAX) { + if (imageViewImpl->range.aspect == PAL_IMAGE_ASPECT_COLOR) { + RTVHeapAllocator* allocator = &device->rtvAllocator; + allocator->freeList[imageViewImpl->heapIndex] = allocator->freeTop; + allocator->freeTop = imageViewImpl->heapIndex; + + } else { + DSVHeapAllocator* allocator = &device->dsvAllocator; + allocator->freeList[imageViewImpl->heapIndex] = allocator->freeTop; + allocator->freeTop = imageViewImpl->heapIndex; + } + } + palFree(s_D3D12.allocator, imageViewImpl); +} + +PalResult PAL_CALL createSamplerD3D12( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + if (info->maxAnisotropy > deviceImpl->limits.maxAnisotropy) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + SamplerD3D12* sampler = nullptr; + sampler = palAllocate(s_D3D12.allocator, sizeof(SamplerD3D12), 0); + if (!sampler) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(sampler, 0, sizeof(SamplerD3D12)); + sampler->desc.MaxLOD = info->maxLod; + sampler->desc.MinLOD = info->minLod; + sampler->desc.MipLODBias = info->mipLodBias; + + sampler->desc.MaxAnisotropy = 1; + if (info->enableAnisotropy) { + sampler->desc.MaxAnisotropy = (UINT)info->maxAnisotropy; + } + + sampler->desc.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + if (info->enableCompare) { + sampler->desc.ComparisonFunc = compareOpToD3D12(info->compareOp); + } + + borderColorToD3D12(info->borderColor, sampler->desc.BorderColor); + sampler->desc.AddressU = addressModeToD3D12(info->addressModeU); + sampler->desc.AddressV = addressModeToD3D12(info->addressModeV); + sampler->desc.AddressW = addressModeToD3D12(info->addressModeW); + + sampler->desc.Filter = + filterToD3D12(info->minFilterMode, info->magFilterMode, info->mipmapMode); + + *outSampler = (PalSampler*)sampler; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySamplerD3D12(PalSampler* sampler) +{ + palFree(s_D3D12.allocator, sampler); +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_pipeline_d3d12.c b/src/graphics/d3d12/pal_pipeline_d3d12.c new file mode 100644 index 00000000..a8aa98c7 --- /dev/null +++ b/src/graphics/d3d12/pal_pipeline_d3d12.c @@ -0,0 +1,1450 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +#if INTPTR_MAX == INT64_MAX +#define PTR_SIZE 8 +#else +#define PTR_SIZE 4 +#endif // INTPTR_MAX + +#if defined(_MSC_VER) +#define ALIGN_STREAM __declspec(align(PTR_SIZE)) +#elif defined(__GNUC__) || defined(__clang__) +#define ALIGN_STREAM __attribute__((aligned(PTR_SIZE))) +#else +#define ALIGN_STREAM +#endif // _MSC_VER + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + ID3D12RootSignature* root; +} RootSignatureStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_INPUT_LAYOUT_DESC desc; +} InputLayoutStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_PRIMITIVE_TOPOLOGY_TYPE topology; +} TopologyStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value; +} IBStripCutStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_RASTERIZER_DESC desc; +} RasterizerStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + DXGI_SAMPLE_DESC desc; +} SampleDescStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + UINT mask; +} SampleMaskStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_DEPTH_STENCIL_DESC desc; +} DepthStencilStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_BLEND_DESC desc; +} BlendStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + struct D3D12_RT_FORMAT_ARRAY data; +} RTVStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + DXGI_FORMAT format; +} DSVStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_SHADER_BYTECODE desc; +} ShaderStream; + +typedef ALIGN_STREAM struct { + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + D3D12_VIEW_INSTANCING_DESC desc; +} ViewInstancingStream; + +typedef struct { + RootSignatureStream layout; + InputLayoutStream inputLayout; + TopologyStream topology; + IBStripCutStream ibStripCut; + RasterizerStream rasterizer; + SampleDescStream sampleDesc; + SampleMaskStream sampleMask; + DepthStencilStream depthStencil; + BlendStream blend; + RTVStream RTV; + DSVStream DSV; + ViewInstancingStream viewInstancing; + ShaderStream shaders[7]; // 7 shader types for graphics pipeline +} GraphicsPipelineStreamDesc; + +static D3D12_STENCIL_OP stencilOpToD3D12(PalStencilOp op) +{ + switch (op) { + case PAL_STENCIL_OP_KEEP: + return D3D12_STENCIL_OP_KEEP; + + case PAL_STENCIL_OP_ZERO: + return D3D12_STENCIL_OP_ZERO; + + case PAL_STENCIL_OP_REPLACE: + return D3D12_STENCIL_OP_REPLACE; + + case PAL_STENCIL_OP_INCREMENT_AND_CLAMP: + return D3D12_STENCIL_OP_INCR_SAT; + + case PAL_STENCIL_OP_DECREMENT_AND_CLAMP: + return D3D12_STENCIL_OP_DECR_SAT; + + case PAL_STENCIL_OP_INVERT: + return D3D12_STENCIL_OP_INVERT; + + case PAL_STENCIL_OP_INCREMENT_AND_WRAP: + return D3D12_STENCIL_OP_INCR; + + case PAL_STENCIL_OP_DECREMENT_AND_WRAP: + return D3D12_STENCIL_OP_DECR; + } + + return D3D12_STENCIL_OP_KEEP; +} + +static D3D12_BLEND_OP blendOpToD3D12(PalBlendOp op) +{ + switch (op) { + case PAL_BLEND_OP_ADD: + return D3D12_BLEND_OP_ADD; + + case PAL_BLEND_OP_SUBTRACT: + return D3D12_BLEND_OP_SUBTRACT; + + case PAL_BLEND_OP_REVERSE_SUBTRACT: + return D3D12_BLEND_OP_REV_SUBTRACT; + + case PAL_BLEND_OP_MIN: + return D3D12_BLEND_OP_MIN; + + case PAL_BLEND_OP_MAX: + return D3D12_BLEND_OP_MAX; + } + + return D3D12_BLEND_OP_ADD; +} + +static D3D12_BLEND blendFactorToD3D12(PalBlendFactor op) +{ + switch (op) { + case PAL_BLEND_FACTOR_ZERO: + return D3D12_BLEND_ZERO; + + case PAL_BLEND_FACTOR_ONE: + return D3D12_BLEND_ONE; + + case PAL_BLEND_FACTOR_SRC_COLOR: + return D3D12_BLEND_SRC_COLOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: + return D3D12_BLEND_INV_SRC_COLOR; + + case PAL_BLEND_FACTOR_DST_COLOR: + return D3D12_BLEND_DEST_COLOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR: + return D3D12_BLEND_INV_DEST_COLOR; + + case PAL_BLEND_FACTOR_SRC_ALPHA: + return D3D12_BLEND_SRC_ALPHA; + + case PAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: + return D3D12_BLEND_INV_SRC_ALPHA; + + case PAL_BLEND_FACTOR_DST_ALPHA: + return D3D12_BLEND_DEST_ALPHA; + + case PAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: + return D3D12_BLEND_INV_DEST_ALPHA; + + case PAL_BLEND_FACTOR_CONSTANT_COLOR: + case PAL_BLEND_FACTOR_CONSTANT_ALPHA: + return D3D12_BLEND_BLEND_FACTOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: + case PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: + return D3D12_BLEND_INV_BLEND_FACTOR; + } + + return D3D12_BLEND_ZERO; +} + +static D3D_PRIMITIVE_TOPOLOGY getPatchTopology(uint32_t patch) +{ + switch (patch) { + case 1: + return D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST; + + case 2: + return D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST; + + case 3: + return D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST; + + case 4: + return D3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST; + + case 5: + return D3D_PRIMITIVE_TOPOLOGY_5_CONTROL_POINT_PATCHLIST; + + case 6: + return D3D_PRIMITIVE_TOPOLOGY_6_CONTROL_POINT_PATCHLIST; + + case 7: + return D3D_PRIMITIVE_TOPOLOGY_7_CONTROL_POINT_PATCHLIST; + + case 8: + return D3D_PRIMITIVE_TOPOLOGY_8_CONTROL_POINT_PATCHLIST; + + case 9: + return D3D_PRIMITIVE_TOPOLOGY_9_CONTROL_POINT_PATCHLIST; + + case 10: + return D3D_PRIMITIVE_TOPOLOGY_10_CONTROL_POINT_PATCHLIST; + + case 11: + return D3D_PRIMITIVE_TOPOLOGY_11_CONTROL_POINT_PATCHLIST; + + case 12: + return D3D_PRIMITIVE_TOPOLOGY_12_CONTROL_POINT_PATCHLIST; + + case 13: + return D3D_PRIMITIVE_TOPOLOGY_13_CONTROL_POINT_PATCHLIST; + + case 14: + return D3D_PRIMITIVE_TOPOLOGY_14_CONTROL_POINT_PATCHLIST; + + case 15: + return D3D_PRIMITIVE_TOPOLOGY_15_CONTROL_POINT_PATCHLIST; + + case 16: + return D3D_PRIMITIVE_TOPOLOGY_16_CONTROL_POINT_PATCHLIST; + + case 17: + return D3D_PRIMITIVE_TOPOLOGY_17_CONTROL_POINT_PATCHLIST; + + case 18: + return D3D_PRIMITIVE_TOPOLOGY_18_CONTROL_POINT_PATCHLIST; + + case 19: + return D3D_PRIMITIVE_TOPOLOGY_19_CONTROL_POINT_PATCHLIST; + + case 20: + return D3D_PRIMITIVE_TOPOLOGY_20_CONTROL_POINT_PATCHLIST; + + case 21: + return D3D_PRIMITIVE_TOPOLOGY_21_CONTROL_POINT_PATCHLIST; + + case 22: + return D3D_PRIMITIVE_TOPOLOGY_22_CONTROL_POINT_PATCHLIST; + + case 23: + return D3D_PRIMITIVE_TOPOLOGY_23_CONTROL_POINT_PATCHLIST; + + case 24: + return D3D_PRIMITIVE_TOPOLOGY_24_CONTROL_POINT_PATCHLIST; + + case 25: + return D3D_PRIMITIVE_TOPOLOGY_25_CONTROL_POINT_PATCHLIST; + + case 26: + return D3D_PRIMITIVE_TOPOLOGY_26_CONTROL_POINT_PATCHLIST; + + case 27: + return D3D_PRIMITIVE_TOPOLOGY_27_CONTROL_POINT_PATCHLIST; + + case 28: + return D3D_PRIMITIVE_TOPOLOGY_28_CONTROL_POINT_PATCHLIST; + + case 29: + return D3D_PRIMITIVE_TOPOLOGY_29_CONTROL_POINT_PATCHLIST; + + case 30: + return D3D_PRIMITIVE_TOPOLOGY_30_CONTROL_POINT_PATCHLIST; + } + + return D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST; +} + +static uint32_t getVertexTypeSizeD3D12(PalVertexType type) +{ + // count x sizeof type returned as size + switch (type) { + case PAL_VERTEX_TYPE_INT8_2: + case PAL_VERTEX_TYPE_UINT8_2: + case PAL_VERTEX_TYPE_INT8_2NORM: + case PAL_VERTEX_TYPE_UINT8_2NORM: { + return 2; + } + + case PAL_VERTEX_TYPE_INT32: + case PAL_VERTEX_TYPE_UINT32: + case PAL_VERTEX_TYPE_INT8_4: + case PAL_VERTEX_TYPE_INT8_4NORM: + case PAL_VERTEX_TYPE_UINT8_4: + case PAL_VERTEX_TYPE_UINT8_4NORM: + case PAL_VERTEX_TYPE_INT16_2NORM: + case PAL_VERTEX_TYPE_INT16_2: + case PAL_VERTEX_TYPE_UINT16_2: + case PAL_VERTEX_TYPE_UINT16_2NORM: + case PAL_VERTEX_TYPE_FLOAT: + case PAL_VERTEX_TYPE_HALF_FLOAT16_2: { + return 4; + } + + case PAL_VERTEX_TYPE_INT32_2: + case PAL_VERTEX_TYPE_UINT32_2: + case PAL_VERTEX_TYPE_INT16_4: + case PAL_VERTEX_TYPE_UINT16_4: + case PAL_VERTEX_TYPE_UINT16_4NORM: + case PAL_VERTEX_TYPE_INT16_4NORM: + case PAL_VERTEX_TYPE_FLOAT2: + case PAL_VERTEX_TYPE_HALF_FLOAT16_4: { + return 8; + } + + case PAL_VERTEX_TYPE_INT32_3: + case PAL_VERTEX_TYPE_UINT32_3: + case PAL_VERTEX_TYPE_FLOAT3: { + return 12; + } + + case PAL_VERTEX_TYPE_INT32_4: + case PAL_VERTEX_TYPE_UINT32_4: + case PAL_VERTEX_TYPE_FLOAT4: { + return 16; + } + } + + return 0; +} + +static void getHitGroupNameD3D12( + uint32_t index, + wchar_t dst[PAL_SHADER_ENTRY_NAME_SIZE]) +{ + wcscpy(dst, L"HitGroup"); + _itow(index, dst + 8, 10); +} + +static const char* semanticIDToStringD3D12(PalVertexSemanticID id) +{ + switch (id) { + case PAL_VERTEX_SEMANTIC_ID_POSITION: + return "POSITION"; + + case PAL_VERTEX_SEMANTIC_ID_COLOR: + return "COLOR"; + + case PAL_VERTEX_SEMANTIC_ID_TEXCOORD: + return "TEXCOORD"; + + case PAL_VERTEX_SEMANTIC_ID_NORMAL: + return "NORMAL"; + + case PAL_VERTEX_SEMANTIC_ID_TANGENT: + return "TANGENT"; + } + return nullptr; +} + +PalResult PAL_CALL createPipelineLayoutD3D12( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + PipelineLayoutD3D12* layout = nullptr; + uint32_t resourceCount = 0; + uint32_t samplerCount = 0; + uint64_t pushConstantSize = 0; + uint32_t sizeInBytes = sizeof(D3D12_DESCRIPTOR_RANGE1); + + uint32_t rangesOffset = 0; + uint32_t samplerRangesOffset = 0; + D3D12_DESCRIPTOR_RANGE1* ranges = nullptr; + D3D12_DESCRIPTOR_RANGE1* samplerRanges = nullptr; + + uint32_t parameterCount = 0; + D3D12_ROOT_PARAMETER1* parameters = nullptr; + D3D12_ROOT_SIGNATURE_FLAGS rootFlags = 0; + rootFlags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + if (info->descriptorSetLayoutCount > deviceImpl->limits.maxBoundDescriptorSets) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (deviceImpl->shaderModel >= PAL_MAKE_SHADER_TARGET(6, 6)) { + rootFlags |= D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED; + rootFlags |= D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED; + } + + // get the total resource and sampler ranges for all provided descriptor set layouts + for (int i = 0; i < info->descriptorSetLayoutCount; i++) { + DescriptorSetLayoutD3D12* tmp = (DescriptorSetLayoutD3D12*)info->descriptorSetLayouts[i]; + resourceCount += tmp->bindingCount - tmp->samplerCount; + samplerCount += tmp->samplerCount; + + if (tmp->bindingCount - tmp->samplerCount >= 1) { + parameterCount++; + } + + if (tmp->samplerCount >= 1) { + parameterCount++; + } + } + + if (info->usePushConstant) { + pushConstantSize = info->pushConstantInfo.offset + info->pushConstantInfo.size; + if (pushConstantSize > deviceImpl->limits.maxPushConstantSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (pushConstantSize) { + parameterCount++; + } + } + + layout = palAllocate(s_D3D12.allocator, sizeof(PipelineLayoutD3D12), 0); + if (!layout) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (parameterCount) { + uint32_t paramtersSize = sizeof(D3D12_ROOT_PARAMETER1) * parameterCount; + parameters = palAllocate(s_D3D12.allocator, paramtersSize, 0); + if (!parameters) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(parameters, 0, paramtersSize); + } + + if (resourceCount) { + ranges = palAllocate(s_D3D12.allocator, sizeInBytes * resourceCount, 0); + if (!ranges) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + if (samplerCount) { + samplerRanges = palAllocate(s_D3D12.allocator, sizeInBytes * samplerCount, 0); + if (!samplerRanges) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + // write root constant first if its provided + parameterCount = 0; // reset and reuse the same variable + layout->constantIndex = UINT32_MAX; + if (pushConstantSize && info->usePushConstant) { + D3D12_ROOT_PARAMETER1* parameter = ¶meters[parameterCount]; + parameter->ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + parameter->Constants.Num32BitValues = (UINT)pushConstantSize / 4; + parameter->ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + layout->constantIndex = parameterCount; + parameterCount++; + } + + uint32_t registerSpace = 0; + for (int i = 0; i < info->descriptorSetLayoutCount; i++) { + DescriptorSetLayoutD3D12* tmp = (DescriptorSetLayoutD3D12*)info->descriptorSetLayouts[i]; + D3D12_ROOT_PARAMETER1* parameter = nullptr; + + // reset and reuse same variable + resourceCount = tmp->bindingCount - tmp->samplerCount; + samplerCount = tmp->samplerCount; + + uint32_t samplerIndex = samplerRangesOffset; + uint32_t rangeIndex = rangesOffset; + + // seperate the samplers from the remaining descriptors + for (int j = 0; j < tmp->bindingCount; j++) { + DescriptorSetBinding* binding = &tmp->bindings[j]; + D3D12_DESCRIPTOR_RANGE1* tmpRange = nullptr; + + if (binding->type == PAL_DESCRIPTOR_TYPE_SAMPLER) { + tmpRange = &samplerRanges[samplerIndex++]; + } else { + tmpRange = &ranges[rangeIndex++]; + } + + *tmpRange = binding->range; + tmpRange->RegisterSpace = registerSpace; + } + + // resource ranges + if (resourceCount) { + parameter = ¶meters[parameterCount]; + parameter->ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + parameter->DescriptorTable.NumDescriptorRanges = resourceCount; + parameter->DescriptorTable.pDescriptorRanges = &ranges[rangesOffset]; + parameter->ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + rangesOffset += resourceCount; + parameterCount++; + } + + // sampler ranges + if (samplerCount) { + parameter = ¶meters[parameterCount]; + parameter->ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + parameter->DescriptorTable.NumDescriptorRanges = samplerCount; + parameter->DescriptorTable.pDescriptorRanges = &samplerRanges[samplerRangesOffset]; + parameter->ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + samplerRangesOffset += samplerCount; + parameterCount++; + } + + registerSpace++; + } + + // create root signature + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootDesc = {0}; + rootDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + rootDesc.Desc_1_1.NumParameters = parameterCount; + rootDesc.Desc_1_1.pParameters = parameters; + rootDesc.Desc_1_1.Flags = rootFlags; + + ID3DBlob* blob = nullptr; + HRESULT result = s_D3D12.serializeVersionedRootSignature(&rootDesc, &blob, nullptr); + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + result = deviceImpl->handle->lpVtbl->CreateRootSignature( + deviceImpl->handle, + 0, + blob->lpVtbl->GetBufferPointer(blob), + blob->lpVtbl->GetBufferSize(blob), + &IID_RootSignature, + (void**)&layout->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + if (resourceCount) { + palFree(s_D3D12.allocator, ranges); + } + + if (samplerCount) { + palFree(s_D3D12.allocator, samplerRanges); + } + + if (parameterCount) { + palFree(s_D3D12.allocator, parameters); + } + blob->lpVtbl->Release(blob); + + *outLayout = (PalPipelineLayout*)layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyPipelineLayoutD3D12(PalPipelineLayout* layout) +{ + PipelineLayoutD3D12* layoutImpl = (PipelineLayoutD3D12*)layout; + layoutImpl->handle->lpVtbl->Release(layoutImpl->handle); + palFree(s_D3D12.allocator, layoutImpl); +} + +PalResult PAL_CALL createGraphicsPipelineD3D12( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + HRESULT result; + uint32_t patchControlPoints = 0; + uint32_t totalSize = 0; + PalBool alphaToCoverageEnable = PAL_FALSE; + PipelineD3D12* pipeline = nullptr; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + PipelineLayoutD3D12* layout = (PipelineLayoutD3D12*)info->pipelineLayout; + + D3D12_INPUT_ELEMENT_DESC* elementDescs = nullptr; + D3D12_VIEW_INSTANCE_LOCATION* viewLocations = nullptr; + + GraphicsPipelineStreamDesc graphicsStreamDesc = {0}; + memset(&graphicsStreamDesc, 0, sizeof(GraphicsPipelineStreamDesc)); + + pipeline = palAllocate(s_D3D12.allocator, sizeof(PipelineD3D12), 0); + if (!pipeline) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + memset(pipeline, 0, sizeof(PipelineD3D12)); + + if (info->renderingLayout->viewCount > 1) { + viewLocations = palAllocate( + s_D3D12.allocator, + sizeof(D3D12_VIEW_INSTANCE_LOCATION) * info->renderingLayout->viewCount, + 0); + + if (!viewLocations) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + // Root signature + RootSignatureStream* rootSignatureStream = &graphicsStreamDesc.layout; + totalSize += sizeof(RootSignatureStream); + rootSignatureStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE; + rootSignatureStream->root = layout->handle; + + // shaders + for (int i = 0; i < info->shaderCount; i++) { + ShaderD3D12* tmp = (ShaderD3D12*)info->shaders[i]; + ShaderStream* shaderStream = &graphicsStreamDesc.shaders[i]; + totalSize += sizeof(ShaderStream); + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type; + + ShaderEntry* entry = &tmp->entries[0]; + if (entry->patchControlPoints) { + patchControlPoints = entry->patchControlPoints; + if (info->topology != PAL_PRIMITIVE_TOPOLOGY_PATCH) { + palFree(s_D3D12.allocator, pipeline); + return PAL_RESULT_CODE_INVALID_OPERATION; + } + } + + if (entry->stage == PAL_SHADER_STAGE_VERTEX) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VS; + + } else if (entry->stage == PAL_SHADER_STAGE_FRAGMENT) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS; + + } else if (entry->stage == PAL_SHADER_STAGE_GEOMETRY) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_GS; + + } else if (entry->stage == PAL_SHADER_STAGE_TESSELLATION_CONTROL) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_HS; + + } else if (entry->stage == PAL_SHADER_STAGE_TESSELLATION_EVALUATION) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DS; + + } else if (entry->stage == PAL_SHADER_STAGE_TASK) { + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS; + + } else { + // mesh shader + type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS; + } + + shaderStream->type = type; + shaderStream->desc = tmp->byteCode; + } + + // Vertex input state + // get the max size of vertex attributes in all layouts + uint32_t vertexCount = 0; + uint32_t vertexLayoutCount = info->vertexLayoutCount; + for (int i = 0; i < vertexLayoutCount; i++) { + PalVertexLayout* layout = &info->vertexLayouts[i]; + vertexCount += layout->attributeCount; + } + + if (info->vertexLayoutCount > deviceImpl->limits.maxVertexLayouts) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (vertexCount > deviceImpl->limits.maxVertexAttributes) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + InputLayoutStream* inputLayoutStream = &graphicsStreamDesc.inputLayout; + totalSize += sizeof(InputLayoutStream); + inputLayoutStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_INPUT_LAYOUT; + + pipeline->strides = nullptr; + if (vertexCount) { + pipeline->strides = palAllocate(s_D3D12.allocator, sizeof(uint32_t) * 8, 0); + if (!pipeline->strides) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + elementDescs = + palAllocate(s_D3D12.allocator, sizeof(D3D12_INPUT_ELEMENT_DESC) * vertexCount, 0); + + if (!elementDescs) { + palFree(s_D3D12.allocator, elementDescs); + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + uint32_t positionIndex = 0; + uint32_t colorIndex = 0; + uint32_t texCoordIndex = 0; + uint32_t normalIndex = 0; + uint32_t tangentIndex = 0; + + for (int i = 0; i < info->vertexLayoutCount; i++) { + PalVertexLayout* layout = &info->vertexLayouts[i]; + uint32_t stride = 0; + uint32_t offset = 0; + + for (int j = 0; j < layout->attributeCount; j++) { + PalVertexAttribute* vertexAttrib = &layout->attributes[j]; + D3D12_INPUT_ELEMENT_DESC* elementDesc = &elementDescs[j]; + + elementDesc->Format = vertexTypeToD3D12(vertexAttrib->type); + elementDesc->InputSlot = layout->binding; + elementDesc->SemanticName = semanticIDToStringD3D12(vertexAttrib->semanticID); + + if (vertexAttrib->semanticID == PAL_VERTEX_SEMANTIC_ID_POSITION) { + elementDesc->SemanticIndex = positionIndex++; + + } else if (vertexAttrib->semanticID == PAL_VERTEX_SEMANTIC_ID_COLOR) { + elementDesc->SemanticIndex = colorIndex++; + + } else if (vertexAttrib->semanticID == PAL_VERTEX_SEMANTIC_ID_TEXCOORD) { + elementDesc->SemanticIndex = texCoordIndex++; + + } else if (vertexAttrib->semanticID == PAL_VERTEX_SEMANTIC_ID_NORMAL) { + elementDesc->SemanticIndex = normalIndex++; + + } else { + // tangent + elementDesc->SemanticIndex = tangentIndex++; + } + + if (layout->type == PAL_VERTEX_LAYOUT_TYPE_PER_INSTANCE) { + elementDesc->InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA; + elementDesc->InstanceDataStepRate = 1; + } else { + elementDesc->InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; + elementDesc->InstanceDataStepRate = 0; + } + + // build offsets and stride + uint32_t size = getVertexTypeSizeD3D12(vertexAttrib->type); + elementDesc->AlignedByteOffset = offset; + offset += size; + stride += size; + } + + // cache the computed stride to be used later by the vertex buffer + pipeline->strides[i] = stride; + } + + inputLayoutStream->desc.NumElements = vertexCount; + inputLayoutStream->desc.pInputElementDescs = elementDescs; + } + + // Primitive Topology + D3D12_PRIMITIVE_TOPOLOGY_TYPE topologyType = 0; + D3D_PRIMITIVE_TOPOLOGY topology = 0; + switch (info->topology) { + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_LIST: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + topology = D3D_PRIMITIVE_TOPOLOGY_LINELIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_STRIP: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + topology = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_POINT_LIST: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; + topology = D3D_PRIMITIVE_TOPOLOGY_POINTLIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_PATCH: { + topologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH; + topology = getPatchTopology(patchControlPoints); + break; + } + } + + TopologyStream* topologyStream = &graphicsStreamDesc.topology; + totalSize += sizeof(TopologyStream); + topologyStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PRIMITIVE_TOPOLOGY; + topologyStream->topology = topologyType; + + // IB Strip Cut + IBStripCutStream* inStripCutStream = &graphicsStreamDesc.ibStripCut; + totalSize += sizeof(IBStripCutStream); + inStripCutStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_IB_STRIP_CUT_VALUE; + if (info->primitiveRestartEnable == PAL_FALSE) { + inStripCutStream->value = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED; + + } else { + if (info->indexType == PAL_INDEX_TYPE_UINT16) { + inStripCutStream->value = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFF; + } else { + inStripCutStream->value = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_0xFFFFFFFF; + } + } + + // Rasterizer + RasterizerStream* rasterizerStream = &graphicsStreamDesc.rasterizer; + totalSize += sizeof(RasterizerStream); + rasterizerStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER; + + rasterizerStream->desc.CullMode = D3D12_CULL_MODE_NONE; + rasterizerStream->desc.FillMode = D3D12_FILL_MODE_SOLID; + rasterizerStream->desc.FrontCounterClockwise = PAL_FALSE; + rasterizerStream->desc.DepthClipEnable = TRUE; + + if (info->rasterizerState) { + PalRasterizerState* state = info->rasterizerState; + if (state->cullMode == PAL_CULL_MODE_NONE) { + rasterizerStream->desc.CullMode = D3D12_CULL_MODE_NONE; + + } else if (state->cullMode == PAL_CULL_MODE_BACK) { + rasterizerStream->desc.CullMode = D3D12_CULL_MODE_BACK; + + } else if (state->cullMode == PAL_CULL_MODE_FRONT) { + rasterizerStream->desc.CullMode = D3D12_CULL_MODE_FRONT; + } + + if (state->polygonMode == PAL_POLYGON_MODE_FILL) { + rasterizerStream->desc.FillMode = D3D12_FILL_MODE_SOLID; + + } else { + rasterizerStream->desc.FillMode = D3D12_FILL_MODE_WIREFRAME; + } + + if (state->frontFace == PAL_FRONT_FACE_CLOCKWISE) { + rasterizerStream->desc.FrontCounterClockwise = PAL_FALSE; + + } else { + rasterizerStream->desc.FrontCounterClockwise = TRUE; + } + + rasterizerStream->desc.DepthClipEnable = !state->enableDepthClamp; + rasterizerStream->desc.DepthBias = (INT)state->depthBiasConstant; + rasterizerStream->desc.SlopeScaledDepthBias = state->depthBiasSlope; + rasterizerStream->desc.DepthBiasClamp = state->depthBiasClamp; + } + + // Sample Desc + SampleDescStream* sampleDescStream = &graphicsStreamDesc.sampleDesc; + totalSize += sizeof(SampleDescStream); + sampleDescStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC; + sampleDescStream->desc.Quality = 0; + sampleDescStream->desc.Count = 1; + + // Sample Mask + SampleMaskStream* sampleMaskStream = &graphicsStreamDesc.sampleMask; + totalSize += sizeof(SampleMaskStream); + sampleMaskStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK; + sampleMaskStream->mask = UINT_MAX; + + if (info->multisampleState) { + PalMultisampleState* state = info->multisampleState; + if (state->sampleMask) { + sampleMaskStream->mask = (UINT)state->sampleMask; + } + + sampleDescStream->desc.Count = samplesToD3D12(state->sampleCount); + alphaToCoverageEnable = state->enableAlphaToCoverage; + } + + // Depth stencil + DepthStencilStream* depthStencilStream = &graphicsStreamDesc.depthStencil; + totalSize += sizeof(DepthStencilStream); + depthStencilStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL; + depthStencilStream->desc.DepthEnable = PAL_FALSE; + depthStencilStream->desc.StencilEnable = PAL_FALSE; + + if (info->depthStencilState) { + PalDepthStencilState* state = info->depthStencilState; + PalStencilOpState* back = &state->backStencilOpState; + PalStencilOpState* front = &state->frontStencilOpState; + + D3D12_DEPTH_STENCILOP_DESC* backDesc = &depthStencilStream->desc.BackFace; + D3D12_DEPTH_STENCILOP_DESC* frontDesc = &depthStencilStream->desc.FrontFace; + + backDesc->StencilFunc = compareOpToD3D12(back->compareOp); + backDesc->StencilDepthFailOp = stencilOpToD3D12(back->depthFailOp); + backDesc->StencilFailOp = stencilOpToD3D12(back->failOp); + backDesc->StencilPassOp = stencilOpToD3D12(back->passOp); + + frontDesc->StencilFunc = compareOpToD3D12(front->compareOp); + frontDesc->StencilDepthFailOp = stencilOpToD3D12(front->depthFailOp); + frontDesc->StencilFailOp = stencilOpToD3D12(front->failOp); + frontDesc->StencilPassOp = stencilOpToD3D12(front->passOp); + + depthStencilStream->desc.DepthFunc = compareOpToD3D12(state->compareOp); + depthStencilStream->desc.DepthEnable = state->enableDepthTest; + depthStencilStream->desc.StencilEnable = state->enableStencilTest; + if (state->enableDepthWrite) { + depthStencilStream->desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + } else { + depthStencilStream->desc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + } + } + + // Blend + BlendStream* blendStream = &graphicsStreamDesc.blend; + totalSize += sizeof(BlendStream); + blendStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND; + blendStream->desc.IndependentBlendEnable = TRUE; + blendStream->desc.AlphaToCoverageEnable = alphaToCoverageEnable; + + if (info->colorBlendAttachmentCount) { + for (int i = 0; i < info->colorBlendAttachmentCount; i++) { + D3D12_RENDER_TARGET_BLEND_DESC* tmp = &blendStream->desc.RenderTarget[i]; + PalColorBlendAttachment* desc = &info->colorBlendAttachments[i]; + + tmp->BlendEnable = desc->enableBlend; + tmp->BlendOpAlpha = blendOpToD3D12(desc->alphaBlendOp); + tmp->BlendOp = blendOpToD3D12(desc->colorBlendOp); + + tmp->SrcBlendAlpha = blendFactorToD3D12(desc->srcAlphaBlendFactor); + tmp->SrcBlend = blendFactorToD3D12(desc->srcColorBlendFactor); + + tmp->DestBlendAlpha = blendFactorToD3D12(desc->dstAlphaBlendFactor); + tmp->DestBlend = blendFactorToD3D12(desc->dstColorBlendFactor); + + // blend color write mask + tmp->RenderTargetWriteMask = 0; + if (desc->colorWriteMask & PAL_COLOR_MASK_RED) { + tmp->RenderTargetWriteMask |= D3D12_COLOR_WRITE_ENABLE_RED; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_GREEN) { + tmp->RenderTargetWriteMask |= D3D12_COLOR_WRITE_ENABLE_GREEN; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_BLUE) { + tmp->RenderTargetWriteMask |= D3D12_COLOR_WRITE_ENABLE_BLUE; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_ALPHA) { + tmp->RenderTargetWriteMask |= D3D12_COLOR_WRITE_ENABLE_ALPHA; + } + } + } + + // Fragment shading rate + pipeline->hasFsr = PAL_FALSE; + if (info->fragmentShadingRateState) { + PalFragmentShadingRateState* state = info->fragmentShadingRateState; + pipeline->shadingRate = shadingRateToD3D12(state->rate); + for (int i = 0; i < 2; i++) { + pipeline->combinerOps[i] = combinerOpsToD3D12(state->combinerOps[i]); + } + } + + // RTV Formats + RTVStream* rtvStream = &graphicsStreamDesc.RTV; + totalSize += sizeof(RTVStream); + rtvStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS; + + DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN; + rtvStream->data.NumRenderTargets = info->renderingLayout->colorAttachentCount; + for (int i = 0; i < info->renderingLayout->colorAttachentCount; i++) { + format = formatToD3D12(info->renderingLayout->colorAttachmentsFormat[i]); + rtvStream->data.RTFormats[i] = format; + } + + // DSV Format + DSVStream* dsvStream = &graphicsStreamDesc.DSV; + totalSize += sizeof(DSVStream); + dsvStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT; + dsvStream->format = formatToD3D12(info->renderingLayout->depthStencilAttachmentFormat); + + // View Instancing + ViewInstancingStream* viewInstacingStream = &graphicsStreamDesc.viewInstancing; + totalSize += sizeof(ViewInstancingStream); + viewInstacingStream->type = D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_VIEW_INSTANCING; + + viewInstacingStream->desc.ViewInstanceCount = 0; + viewInstacingStream->desc.pViewInstanceLocations = nullptr; + if (info->renderingLayout->viewCount > 1) { + for (int i = 0; i < info->renderingLayout->viewCount; i++) { + viewLocations[i].RenderTargetArrayIndex = i; + viewLocations[i].ViewportArrayIndex = i; + } + + viewInstacingStream->desc.ViewInstanceCount = info->renderingLayout->viewCount; + viewInstacingStream->desc.pViewInstanceLocations = viewLocations; + } + + D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = {0}; + streamDesc.pPipelineStateSubobjectStream = &graphicsStreamDesc; + streamDesc.SizeInBytes = totalSize; + + result = deviceImpl->handle->lpVtbl->CreatePipelineState( + deviceImpl->handle, + &streamDesc, + &IID_PipelineState, + &pipeline->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + if (info->vertexLayoutCount) { + palFree(s_D3D12.allocator, elementDescs); + } + + if (info->renderingLayout->viewCount > 1) { + palFree(s_D3D12.allocator, viewLocations); + } + + pipeline->topology = topology; + pipeline->type = GRAPHICS_PIPELINE; + pipeline->layout = layout; + pipeline->shaderExports = nullptr; + pipeline->localRootSignature = nullptr; + + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createComputePipelineD3D12( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + PipelineLayoutD3D12* layout = (PipelineLayoutD3D12*)info->pipelineLayout; + ShaderD3D12* shader = (ShaderD3D12*)info->computeShader; + PipelineD3D12* pipeline = nullptr; + + pipeline = palAllocate(s_D3D12.allocator, sizeof(PipelineD3D12), 0); + if (!pipeline) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + D3D12_COMPUTE_PIPELINE_STATE_DESC desc = {0}; + desc.CS = shader->byteCode; + desc.pRootSignature = layout->handle; + + HRESULT result = deviceImpl->handle->lpVtbl->CreateComputePipelineState( + deviceImpl->handle, + &desc, + &IID_PipelineState, + &pipeline->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + pipeline->type = COMPUTE_PIPELINE; + pipeline->strides = nullptr; + pipeline->hasFsr = PAL_FALSE; + pipeline->layout = layout; + pipeline->shaderExports = nullptr; + pipeline->localRootSignature = nullptr; + + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createRayTracingPipelineD3D12( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + PipelineLayoutD3D12* layout = (PipelineLayoutD3D12*)info->pipelineLayout; + PipelineD3D12* pipeline = nullptr; + + if (info->maxAttributeSize > deviceImpl->limits.maxHitAttributeSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->maxPayloadSize > deviceImpl->limits.maxPayloadSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->maxRecursionDepth > deviceImpl->limits.maxRecursionDepth) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + D3D12_EXPORT_DESC* exportDescs = nullptr; + D3D12_DXIL_LIBRARY_DESC* libraryDescs = nullptr; + RayHitGroup* hitGroups = nullptr; + D3D12_STATE_SUBOBJECT* subObjects = nullptr; + const wchar_t** localExports = nullptr; + + // find the total number of exports + uint32_t exportCount = 0; + for (int i = 0; i < info->shaderCount; i++) { + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[i]; + exportCount += shader->entryCount; + } + + uint32_t subObjectCount = 0; + uint32_t localExportCount = 0; + ShaderBindingTableInfo sbtInfo = {0}; + + for (int i = 0; i < info->shaderGroupCount; i++) { + PalRayTracingShaderGroupCreateInfo* tmp = &info->shaderGroups[i]; + if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL) { + // check if its raygen, miss or callable + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[tmp->generalShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->generalShaderEntryIndex]; + switch (entry->stage) { + case PAL_SHADER_STAGE_RAYGEN: { + sbtInfo.raygenCount++; + sbtInfo.raygenDataSize = max(sbtInfo.raygenDataSize, tmp->maxDataSize); + break; + } + + case PAL_SHADER_STAGE_MISS: { + sbtInfo.missCount++; + sbtInfo.missDataSize = max(sbtInfo.missDataSize, tmp->maxDataSize); + break; + } + + case PAL_SHADER_STAGE_CALLABLE: { + sbtInfo.callableCount++; + sbtInfo.callableDataSize = max(sbtInfo.callableDataSize, tmp->maxDataSize); + break; + } + } + + } else { + sbtInfo.hitDataSize = max(sbtInfo.hitDataSize, tmp->maxDataSize); + sbtInfo.hitCount++; + } + + if (tmp->maxDataSize) { + localExportCount++; + } + } + + // find the max data size across all shader groups + uint32_t localRootSize = 0; + localRootSize = max(localRootSize, sbtInfo.raygenDataSize); + localRootSize = max(localRootSize, sbtInfo.missDataSize); + localRootSize = max(localRootSize, sbtInfo.hitDataSize); + localRootSize = max(localRootSize, sbtInfo.callableDataSize); + + // // D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE + // // D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG + // // D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG + subObjectCount += info->shaderCount + 3; + + subObjectCount += sbtInfo.hitCount; + if (localRootSize) { + // D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE + // D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION + subObjectCount += 2; + } + + pipeline = palAllocate(s_D3D12.allocator, sizeof(PipelineD3D12), 0); + if (!pipeline) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + pipeline->shaderExportCount = exportCount + sbtInfo.hitCount; + subObjects = palAllocate(s_D3D12.allocator, sizeof(D3D12_STATE_SUBOBJECT) * subObjectCount, 0); + hitGroups = palAllocate(s_D3D12.allocator, sizeof(RayHitGroup) * sbtInfo.hitCount, 0); + if (!subObjects || !hitGroups) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + libraryDescs = + palAllocate(s_D3D12.allocator, sizeof(D3D12_DXIL_LIBRARY_DESC) * info->shaderCount, 0); + + exportDescs = palAllocate(s_D3D12.allocator, sizeof(D3D12_EXPORT_DESC) * exportCount, 0); + + pipeline->shaderExports = + palAllocate(s_D3D12.allocator, sizeof(ShaderExport) * pipeline->shaderExportCount, 0); + + localExports = palAllocate(s_D3D12.allocator, sizeof(wchar_t*) * localExportCount, 0); + + if (!libraryDescs || !exportDescs || !pipeline->shaderExports || !localExports) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // global root signature + uint32_t subObjectIndex = 0; + D3D12_GLOBAL_ROOT_SIGNATURE globalRootSignature = {0}; + globalRootSignature.pGlobalRootSignature = layout->handle; + + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; + subObjects[subObjectIndex].pDesc = &globalRootSignature; + subObjectIndex++; + + // ray tracing config + D3D12_RAYTRACING_SHADER_CONFIG shaderConfig = {0}; + shaderConfig.MaxAttributeSizeInBytes = info->maxAttributeSize; + shaderConfig.MaxPayloadSizeInBytes = info->maxPayloadSize; + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG; + subObjects[subObjectIndex].pDesc = &shaderConfig; + subObjectIndex++; + + D3D12_RAYTRACING_PIPELINE_CONFIG pipelineConfig = {0}; + pipelineConfig.MaxTraceRecursionDepth = info->maxRecursionDepth; + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG; + subObjects[subObjectIndex].pDesc = &pipelineConfig; + subObjectIndex++; + + // shaders + uint32_t exportsOffset = 0; + for (int i = 0; i < info->shaderCount; i++) { + ShaderD3D12* tmp = (ShaderD3D12*)info->shaders[i]; + D3D12_DXIL_LIBRARY_DESC* libraryDesc = &libraryDescs[i]; + libraryDesc->DXILLibrary = tmp->byteCode; + + for (int j = 0; j < tmp->entryCount; j++) { + D3D12_EXPORT_DESC* exportDesc = &exportDescs[exportsOffset + j]; + ShaderExport* shaderExport = &pipeline->shaderExports[exportsOffset + j]; + ShaderEntry* entry = &tmp->entries[j]; + + shaderExport->isHitGroup = PAL_FALSE; + shaderExport->stage = entry->stage; + wcscpy(shaderExport->entryName, entry->entryName); + + exportDesc->ExportToRename = nullptr; + exportDesc->Flags = D3D12_EXPORT_FLAG_NONE; + exportDesc->Name = shaderExport->entryName; + } + + libraryDesc->pExports = &exportDescs[exportsOffset]; + libraryDesc->NumExports = tmp->entryCount; + + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY; + subObjects[subObjectIndex].pDesc = libraryDesc; + subObjectIndex++; + exportsOffset += tmp->entryCount; + } + + // hit groups + uint32_t localExportIndex = 0; + uint32_t hitGroupIndex = 0; + for (int i = 0; i < info->shaderGroupCount; i++) { + const wchar_t* exportName = nullptr; + PalRayTracingShaderGroupCreateInfo* tmp = &info->shaderGroups[i]; + if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL) { + if (tmp->maxDataSize) { + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[tmp->generalShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->generalShaderEntryIndex]; + localExports[localExportIndex++] = entry->entryName; + } + + continue; + } + + D3D12_HIT_GROUP_DESC* group = &hitGroups[hitGroupIndex].desc; + getHitGroupNameD3D12(hitGroupIndex, hitGroups[hitGroupIndex].entryName); + + ShaderExport* hitGroupExport = &pipeline->shaderExports[exportCount++]; + wcscpy(hitGroupExport->entryName, hitGroups[hitGroupIndex].entryName); + hitGroupExport->isHitGroup = PAL_TRUE; + hitGroupExport->stage = PAL_SHADER_STAGE_CLOSEST_HIT; // to identify + + group->AnyHitShaderImport = nullptr; + group->ClosestHitShaderImport = nullptr; + group->IntersectionShaderImport = nullptr; + group->HitGroupExport = hitGroups[hitGroupIndex].entryName; + + if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT) { + group->Type = D3D12_HIT_GROUP_TYPE_TRIANGLES; + + } else if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT) { + group->Type = D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE; + } + + // Any hit shader + if (tmp->anyHitShaderIndex != PAL_UNUSED_SHADER_INDEX) { + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[tmp->anyHitShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->anyHitShaderEntryIndex]; + group->AnyHitShaderImport = entry->entryName; + + } else { + group->AnyHitShaderImport = nullptr; + } + + // Closest hit shader + if (tmp->closestHitShaderIndex != PAL_UNUSED_SHADER_INDEX) { + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[tmp->closestHitShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->closestHitShaderEntryIndex]; + group->ClosestHitShaderImport = entry->entryName; + + } else { + group->ClosestHitShaderImport = nullptr; + } + + // IntersectionShader shader + if (tmp->intersectionShaderIndex != PAL_UNUSED_SHADER_INDEX) { + ShaderD3D12* shader = (ShaderD3D12*)info->shaders[tmp->intersectionShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->intersectionShaderEntryIndex]; + group->IntersectionShaderImport = entry->entryName; + + } else { + group->IntersectionShaderImport = nullptr; + } + + if (tmp->maxDataSize) { + localExports[localExportIndex] = group->HitGroupExport; + } + + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; + subObjects[subObjectIndex].pDesc = group; + subObjectIndex++; + hitGroupIndex++; + localExportIndex++; + } + + // check if we need a local root signature + D3D12_SUBOBJECT_TO_EXPORTS_ASSOCIATION localExportAssociation = {0}; + D3D12_LOCAL_ROOT_SIGNATURE localRootSignature = {0}; + if (localRootSize) { + D3D12_ROOT_PARAMETER1 parameter = {0}; + parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + parameter.Constants.Num32BitValues = _ALIGN(localRootSize, 4) / 4; + parameter.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + + D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootDesc = {0}; + rootDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1; + rootDesc.Desc_1_1.NumParameters = 1; + rootDesc.Desc_1_1.pParameters = ¶meter; + rootDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE; + + ID3DBlob* blob = nullptr; + result = s_D3D12.serializeVersionedRootSignature(&rootDesc, &blob, nullptr); + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + result = deviceImpl->handle->lpVtbl->CreateRootSignature( + deviceImpl->handle, + 0, + blob->lpVtbl->GetBufferPointer(blob), + blob->lpVtbl->GetBufferSize(blob), + &IID_RootSignature, + (void**)&pipeline->localRootSignature); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + localRootSignature.pLocalRootSignature = pipeline->localRootSignature; + subObjects[subObjectIndex].Type = D3D12_STATE_SUBOBJECT_TYPE_LOCAL_ROOT_SIGNATURE; + subObjects[subObjectIndex].pDesc = &localRootSignature; + + localExportAssociation.pSubobjectToAssociate = &subObjects[subObjectIndex]; + localExportAssociation.pExports = localExports; + localExportAssociation.NumExports = localExportCount; + subObjectIndex++; + + D3D12_STATE_SUBOBJECT_TYPE t = D3D12_STATE_SUBOBJECT_TYPE_SUBOBJECT_TO_EXPORTS_ASSOCIATION; + subObjects[subObjectIndex].Type = t; + subObjects[subObjectIndex].pDesc = &localExportAssociation; + subObjectIndex++; + } + + D3D12_STATE_OBJECT_DESC desc = {0}; + desc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE; + desc.NumSubobjects = subObjectCount; + desc.pSubobjects = subObjects; + + result = deviceImpl->handle->lpVtbl->CreateStateObject( + deviceImpl->handle, + &desc, + &IID_StateObject, + &pipeline->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + palFree(s_D3D12.allocator, hitGroups); + palFree(s_D3D12.allocator, subObjects); + palFree(s_D3D12.allocator, libraryDescs); + palFree(s_D3D12.allocator, exportDescs); + palFree(s_D3D12.allocator, localExports); + + pipeline->type = RAY_TRACING_PIPELINE; + pipeline->strides = nullptr; + pipeline->hasFsr = PAL_FALSE; + pipeline->layout = layout; + + pipeline->sbtInfo = sbtInfo; + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyPipelineD3D12(PalPipeline* pipeline) +{ + PipelineD3D12* pipelineImpl = (PipelineD3D12*)pipeline; + if (pipelineImpl->type == RAY_TRACING_PIPELINE) { + ID3D12StateObject* handle = pipelineImpl->handle; + handle->lpVtbl->Release(handle); + + } else { + ID3D12PipelineState* handle = pipelineImpl->handle; + handle->lpVtbl->Release(handle); + } + + if (pipelineImpl->localRootSignature) { + pipelineImpl->localRootSignature->lpVtbl->Release(pipelineImpl->localRootSignature); + } + + if (pipelineImpl->strides) { + palFree(s_D3D12.allocator, pipelineImpl->strides); + } + + if (pipelineImpl->shaderExports) { + palFree(s_D3D12.allocator, pipelineImpl->shaderExports); + } + + palFree(s_D3D12.allocator, pipelineImpl); +} + +#endif // PAL_HAS_D3D12_BACKEND diff --git a/src/graphics/d3d12/pal_sbt_d3d12.c b/src/graphics/d3d12/pal_sbt_d3d12.c new file mode 100644 index 00000000..8a757656 --- /dev/null +++ b/src/graphics/d3d12/pal_sbt_d3d12.c @@ -0,0 +1,410 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +PalResult PAL_CALL createShaderBindingTableD3D12( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + ShaderBindingTableD3D12* sbt = nullptr; + PipelineD3D12* pipeline = (PipelineD3D12*)info->rayTracingPipeline; + ShaderBindingTableInfo* sbtInfo = &pipeline->sbtInfo; + + uint32_t totalGroups = sbtInfo->raygenCount + sbtInfo->hitCount; + totalGroups += sbtInfo->missCount + sbtInfo->callableCount; + if (info->recordCount != totalGroups) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + void** raygenHandles = nullptr; + void** missHandles = nullptr; + void** hitHandles = nullptr; + void** callableHandles = nullptr; + + sbt = palAllocate(s_D3D12.allocator, sizeof(ShaderBindingTableD3D12), 0); + if (!sbt) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(sbt, 0, sizeof(ShaderBindingTableD3D12)); + if (sbtInfo->raygenCount) { + raygenHandles = palAllocate(s_D3D12.allocator, sizeof(void*) * sbtInfo->raygenCount, 0); + if (!raygenHandles) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + if (sbtInfo->missCount) { + missHandles = palAllocate(s_D3D12.allocator, sizeof(void*) * sbtInfo->missCount, 0); + if (!missHandles) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + if (sbtInfo->hitCount) { + hitHandles = palAllocate(s_D3D12.allocator, sizeof(void*) * sbtInfo->hitCount, 0); + if (!hitHandles) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + if (sbtInfo->callableCount) { + callableHandles = palAllocate(s_D3D12.allocator, sizeof(void*) * sbtInfo->callableCount, 0); + if (!callableHandles) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + // create SBT buffer + ID3D12StateObject* handle = pipeline->handle; + ID3D12StateObjectProperties* props = NULL; + result = handle->lpVtbl->QueryInterface(handle, &IID_StateObjectProps, (void**)&props); + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + uint32_t groupHandleSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; + uint32_t groupHandleAlignment = D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT; + uint32_t groupBaseAlignment = D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT; + + // get the max local data size + for (int i = 0; i < info->recordCount; i++) { + PalShaderBindingTableRecordInfo* record = &info->records[i]; + uint32_t index = record->groupIndex; + + if (index < sbtInfo->raygenCount) { + // raygen group + if (record->localDataSize > sbtInfo->raygenDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount) { + // miss group + if (record->localDataSize > sbtInfo->missDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount) { + // hit group + if (record->localDataSize > sbtInfo->hitDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else { + // callable group + if (record->localDataSize > sbtInfo->callableDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + } + + // get strides + uint32_t raygenStride = 0; + uint32_t missStride = 0; + uint32_t hitStride = 0; + uint32_t callableStride = 0; + + raygenStride = _ALIGN(groupHandleSize + sbtInfo->raygenDataSize, groupHandleAlignment); + missStride = _ALIGN(groupHandleSize + sbtInfo->missDataSize, groupHandleAlignment); + hitStride = _ALIGN(groupHandleSize + sbtInfo->hitDataSize, groupHandleAlignment); + callableStride = _ALIGN(groupHandleSize + sbtInfo->callableDataSize, groupHandleAlignment); + + // get region size + uint32_t raygenRegionSize = raygenStride * sbtInfo->raygenCount; + uint32_t missRegionSize = missStride * sbtInfo->missCount; + uint32_t hitRegionSize = hitStride * sbtInfo->hitCount; + uint32_t callableRegionSize = callableStride * sbtInfo->callableCount; + + // get offsets + uint32_t offset = 0; + uint32_t raygenOffset = 0; + uint32_t missOffset = 0; + uint32_t hitOffset = 0; + uint32_t callableOffset = 0; + + raygenOffset = _ALIGN(offset, groupBaseAlignment); + offset = raygenOffset + raygenRegionSize; + + missOffset = _ALIGN(offset, groupBaseAlignment); + offset = missOffset + missRegionSize; + + hitOffset = _ALIGN(offset, groupBaseAlignment); + offset = hitOffset + hitRegionSize; + + callableOffset = _ALIGN(offset, groupBaseAlignment); + offset = callableOffset + callableRegionSize; + uint32_t bufferSize = _ALIGN(offset, groupBaseAlignment); + + // create gpu buffer + D3D12_HEAP_PROPERTIES heapProps = {0}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + + D3D12_RESOURCE_DESC bufferDesc = {0}; + bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + bufferDesc.Width = bufferSize; + bufferDesc.Height = 1; + bufferDesc.DepthOrArraySize = 1; + bufferDesc.MipLevels = 1; + bufferDesc.SampleDesc.Count = 1; + bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &bufferDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + &IID_Resource, + (void**)&sbt->buffer); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // create staging buffer + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + result = deviceImpl->handle->lpVtbl->CreateCommittedResource( + deviceImpl->handle, + &heapProps, + 0, + &bufferDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + &IID_Resource, + (void**)&sbt->stagingBuffer); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // get shader group handles + uint32_t raygenIndex = 0; + uint32_t missIndex = 0; + uint32_t hitIndex = 0; + uint32_t callableIndex = 0; + + for (int i = 0; i < pipeline->shaderExportCount; i++) { + ShaderExport* tmp = &pipeline->shaderExports[i]; + PalShaderStage stage = tmp->stage; + + // skip any hit, closest hit, intersection shaders without isHitGroup PalBool + PalBool isHitGroup = PAL_FALSE; + if (stage == PAL_SHADER_STAGE_ANY_HIT || stage == PAL_SHADER_STAGE_CLOSEST_HIT || + stage == PAL_SHADER_STAGE_INTERSECTION) { + if (tmp->isHitGroup) { + isHitGroup = PAL_TRUE; + + } else { + continue; + } + } + + void* handle = props->lpVtbl->GetShaderIdentifier(props, tmp->entryName); + if (stage == PAL_SHADER_STAGE_RAYGEN) { + raygenHandles[raygenIndex++] = handle; + + } else if (stage == PAL_SHADER_STAGE_MISS) { + missHandles[missIndex++] = handle; + + } else if (isHitGroup) { + hitHandles[hitIndex++] = handle; + + } else if (stage == PAL_SHADER_STAGE_CALLABLE) { + callableHandles[callableIndex++] = handle; + } + } + + // copy handles into the buffer + offset = 0; // reuse variable + void* ptr = nullptr; + result = sbt->stagingBuffer->lpVtbl->Map(sbt->stagingBuffer, 0, nullptr, &ptr); + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + // raygen + for (int i = 0; i < sbtInfo->raygenCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + (i * raygenStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, raygenHandles[i], groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + offset += sbtInfo->raygenCount; + + // miss + for (int i = 0; i < sbtInfo->missCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + missOffset + (i * missStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, missHandles[i], groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + offset += sbtInfo->missCount; + + // hit group + for (int i = 0; i < sbtInfo->hitCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + hitOffset + (i * hitStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, hitHandles[i], groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + offset += sbtInfo->hitCount; + + // callable + for (int i = 0; i < sbtInfo->callableCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + callableOffset + (i * callableStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, callableHandles[i], groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + + sbt->stagingPtr = ptr; + sbt->baseAddress = sbt->buffer->lpVtbl->GetGPUVirtualAddress(sbt->buffer); + + // raygen + if (sbtInfo->raygenCount) { + sbt->raygen.region.StartAddress = sbt->baseAddress; + sbt->raygen.offset = 0; // always + sbt->raygen.startIndex = 0; // always + sbt->raygen.region.SizeInBytes = raygenRegionSize; + sbt->raygen.region.StrideInBytes = raygenStride; + + palFree(s_D3D12.allocator, raygenHandles); + } + + // miss + if (sbtInfo->missCount) { + sbt->miss.offset = missOffset; + sbt->miss.startIndex = sbtInfo->raygenCount; + sbt->miss.region.StartAddress = sbt->baseAddress + missOffset; + sbt->miss.region.SizeInBytes = missRegionSize; + sbt->miss.region.StrideInBytes = missStride; + + palFree(s_D3D12.allocator, missHandles); + } + + // hit + if (sbtInfo->hitCount) { + sbt->hit.offset = hitOffset; + sbt->hit.startIndex = sbtInfo->raygenCount + sbtInfo->missCount; + sbt->hit.region.StartAddress = sbt->baseAddress + hitOffset; + sbt->hit.region.SizeInBytes = hitRegionSize; + sbt->hit.region.StrideInBytes = hitStride; + + palFree(s_D3D12.allocator, hitHandles); + } + + // callable + if (sbtInfo->callableCount) { + sbt->callable.offset = callableOffset; + sbt->callable.startIndex = sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount; + sbt->callable.region.StartAddress = sbt->baseAddress + callableOffset; + sbt->callable.region.SizeInBytes = callableRegionSize; + sbt->callable.region.StrideInBytes = callableStride; + + palFree(s_D3D12.allocator, callableHandles); + } + + props->lpVtbl->Release(props); + sbt->handleSize = groupHandleSize; + sbt->stagingBufferSize = bufferSize; + sbt->pipeline = pipeline; + + sbt->isDirty = PAL_TRUE; // we need to copy from the staging to the gpu buffer + *outSbt = (PalShaderBindingTable*)sbt; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyShaderBindingTableD3D12(PalShaderBindingTable* sbt) +{ + ShaderBindingTableD3D12* sbtImpl = (ShaderBindingTableD3D12*)sbt; + sbtImpl->buffer->lpVtbl->Release(sbtImpl->buffer); + sbtImpl->stagingBuffer->lpVtbl->Unmap(sbtImpl->stagingBuffer, 0, nullptr); + sbtImpl->stagingBuffer->lpVtbl->Release(sbtImpl->stagingBuffer); + palFree(s_D3D12.allocator, sbtImpl); +} + +void PAL_CALL updateShaderBindingTableD3D12( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos) +{ + ShaderBindingTableD3D12* sbtImpl = (ShaderBindingTableD3D12*)sbt; + PipelineD3D12* pipeline = sbtImpl->pipeline; + ShaderBindingTableInfo* sbtInfo = &pipeline->sbtInfo; + + uint64_t stride = 0; + uint64_t offset = 0; + uint32_t startIndex = 0; + + for (int i = 0; i < count; i++) { + PalShaderBindingTableRecordInfo* info = &infos[i]; + + // find the group the record belongs to + uint32_t index = info->groupIndex; + if (index < sbtInfo->raygenCount) { + // raygen group + offset = 0; + stride = sbtImpl->raygen.region.StrideInBytes; + startIndex = sbtImpl->raygen.startIndex; + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount) { + // miss group + offset = sbtImpl->miss.offset; + stride = sbtImpl->miss.region.StrideInBytes; + startIndex = sbtImpl->miss.startIndex; + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount) { + // hit group + offset = sbtImpl->hit.offset; + stride = sbtImpl->hit.region.StrideInBytes; + startIndex = sbtImpl->hit.startIndex; + + } else { + // callable group + offset = sbtImpl->callable.offset; + stride = sbtImpl->callable.region.StrideInBytes; + startIndex = sbtImpl->callable.startIndex; + } + + // write payload + uint32_t localIndex = index - startIndex; + uint8_t* dst = (uint8_t*)sbtImpl->stagingPtr + offset + (localIndex * stride); + memcpy(dst + sbtImpl->handleSize, info->localData, info->localDataSize); + } + + sbtImpl->isDirty = PAL_TRUE; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_swapchain_d3d12.c b/src/graphics/d3d12/pal_swapchain_d3d12.c new file mode 100644 index 00000000..b62f92d6 --- /dev/null +++ b/src/graphics/d3d12/pal_swapchain_d3d12.c @@ -0,0 +1,457 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalResult PAL_CALL createSurfaceD3D12( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface) +{ + SurfaceD3D12* surface = nullptr; + surface = palAllocate(s_D3D12.allocator, sizeof(SurfaceD3D12), 0); + if (!surface) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // validate if the window is valid + if (!IsWindow((HWND)window)) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + surface->handle = window; + *outSurface = (PalSurface*)surface; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySurfaceD3D12(PalSurface* surface) +{ + SurfaceD3D12* d3dSurface = (SurfaceD3D12*)surface; + palFree(s_D3D12.allocator, d3dSurface); +} + +void PAL_CALL getSurfaceCapabilitiesD3D12( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps) +{ + SurfaceD3D12* d3dSurface = (SurfaceD3D12*)surface; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + PalBool supportHDR10 = PAL_FALSE; + IDXGISwapChain1* swapchain1 = nullptr; + IDXGISwapChain3* swapchain3 = nullptr; + + DXGI_SWAP_CHAIN_DESC1 desc = {0}; + desc.Width = 8; + desc.Height = 8; + desc.SampleDesc.Count = 1; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = 2; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + + s_D3D12.factory->lpVtbl->CreateSwapChainForHwnd( + s_D3D12.factory, + (IUnknown*)deviceImpl->queue, + d3dSurface->handle, + &desc, + nullptr, + nullptr, + &swapchain1); + + swapchain1->lpVtbl->QueryInterface(swapchain1, &IID_Swapchain, (void**)&swapchain3); + swapchain1->lpVtbl->Release(swapchain1); + + // check for HDR10 color space support + UINT flags = 0; + swapchain3->lpVtbl->CheckColorSpaceSupport( + swapchain3, + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020, + &flags); + + if (flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) { + supportHDR10 = PAL_TRUE; + } + + BOOL allowTearing = PAL_FALSE; + s_D3D12.factory->lpVtbl->CheckFeatureSupport( + s_D3D12.factory, + DXGI_FEATURE_PRESENT_ALLOW_TEARING, + &allowTearing, + sizeof(allowTearing)); + + caps->minImageCount = 2; + caps->supportedPresentModes = (1u << PAL_PRESENT_MODE_FIFO); + if (allowTearing) { + caps->minImageCount = 3; + caps->supportedPresentModes |= (1u << PAL_PRESENT_MODE_IMMEDIATE); + caps->supportedPresentModes |= (1u << PAL_PRESENT_MODE_MAILBOX); + } + + caps->supportedCompositeAlphas |= (1u << PAL_COMPOSITE_ALPHA_OPAQUE); + + caps->maxImageCount = 8; // safe default + caps->minImageWidth = 1; + caps->minImageHeight = 1; + caps->maxImageWidth = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + caps->maxImageHeight = D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION; + caps->maxImageArrayLayers = 1; + + // check support for the base format + caps->supportedFormats = 0; + D3D12_FEATURE_DATA_FORMAT_SUPPORT formatSupport = {0}; + DXGI_FORMAT baseFormats[PAL_SURFACE_FORMAT_COUNT]; + baseFormats[0] = DXGI_FORMAT_B8G8R8A8_UNORM; + baseFormats[1] = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + baseFormats[2] = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + baseFormats[3] = DXGI_FORMAT_R16G16B16A16_FLOAT; + + for (int i = 0; i < PAL_SURFACE_FORMAT_COUNT; i++) { + formatSupport.Format = baseFormats[i]; + deviceImpl->handle->lpVtbl->CheckFeatureSupport( + deviceImpl->handle, + D3D12_FEATURE_FORMAT_SUPPORT, + &formatSupport, + sizeof(formatSupport)); + + if (formatSupport.Support1 != 0 || formatSupport.Support2 != 0) { + if (baseFormats[i] == DXGI_FORMAT_B8G8R8A8_UNORM) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR); + } + + if (baseFormats[i] == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_BGRA8_SRGB_NONLINEAR); + } + + if (baseFormats[i] == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_RGBA8_UNORM_SRGB_NONLINEAR); + } + + if (baseFormats[i] == DXGI_FORMAT_R16G16B16A16_FLOAT) { + // check HDR10 color space + if (supportHDR10) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_RGBA16_FLOAT_HDR10); + } + } + } + } + + swapchain3->lpVtbl->Release(swapchain3); +} + +PalResult PAL_CALL createSwapchainD3D12( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain) +{ + HRESULT result; + SurfaceD3D12* d3dSurface = (SurfaceD3D12*)surface; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + QueueD3D12* queueImpl = (QueueD3D12*)queue; + SwapchainD3D12* swapchain = nullptr; + PalBool isHDRColorspace = PAL_FALSE; + + if (queueImpl->type != PAL_QUEUE_TYPE_GRAPHICS) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->compositeAlpha != PAL_COMPOSITE_ALPHA_OPAQUE) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->presentMode == PAL_PRESENT_MODE_MAILBOX && info->imageCount < 3) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + swapchain = palAllocate(s_D3D12.allocator, sizeof(SwapchainD3D12), 0); + if (!swapchain) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + IDXGISwapChain1* swapchain1 = nullptr; + DXGI_SWAP_CHAIN_DESC1 desc = {0}; + desc.Width = info->width; + desc.Height = info->height; + desc.SampleDesc.Count = 1; + desc.BufferCount = info->imageCount; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.Stereo = PAL_FALSE; + desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + desc.Scaling = DXGI_SCALING_NONE; + + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + if (info->presentMode == PAL_PRESENT_MODE_FIFO) { + swapchain->presentFlags = 0; + swapchain->syncInterval = 1; + + } else if (info->presentMode == PAL_PRESENT_MODE_IMMEDIATE) { + desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + swapchain->presentFlags = DXGI_PRESENT_ALLOW_TEARING; + swapchain->syncInterval = 0; + + } else { + desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + swapchain->presentFlags = DXGI_PRESENT_ALLOW_TEARING; + swapchain->syncInterval = 0; + } + + PalFormat imageFormat; + if (info->format == PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR) { + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + imageFormat = PAL_FORMAT_B8G8R8A8_UNORM; + + } else if (info->format == PAL_SURFACE_FORMAT_BGRA8_SRGB_NONLINEAR) { + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + imageFormat = PAL_FORMAT_B8G8R8A8_SRGB; + + } else if (info->format == PAL_SURFACE_FORMAT_RGBA8_UNORM_SRGB_NONLINEAR) { + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + imageFormat = PAL_FORMAT_R8G8B8A8_SRGB; + + } else if (info->format == PAL_SURFACE_FORMAT_RGBA16_FLOAT_HDR10) { + desc.Format = DXGI_FORMAT_R16G16B16A16_FLOAT; + imageFormat = PAL_FORMAT_R16G16B16A16_SFLOAT; + isHDRColorspace = PAL_TRUE; + } + + swapchain->format = desc.Format; + swapchain->flags = desc.Flags; + result = s_D3D12.factory->lpVtbl->CreateSwapChainForHwnd( + s_D3D12.factory, + (IUnknown*)queueImpl->handle, + d3dSurface->handle, + &desc, + nullptr, + nullptr, + &swapchain1); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + return makeResultD3D12(result); + } + + swapchain1->lpVtbl->QueryInterface(swapchain1, &IID_Swapchain, (void**)&swapchain->handle); + swapchain1->lpVtbl->Release(swapchain1); + + if (isHDRColorspace) { + swapchain->handle->lpVtbl->SetColorSpace1( + swapchain->handle, + DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + } else { + swapchain->handle->lpVtbl->SetColorSpace1( + swapchain->handle, + DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709); + } + + // get and cache swapchain images + swapchain->images = nullptr; + swapchain->images = palAllocate(s_D3D12.allocator, sizeof(ImageD3D12) * info->imageCount, 0); + if (!swapchain->imageCount) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // fill all images with the creation info + for (int i = 0; i < info->imageCount; i++) { + ID3D12Resource* tmp = nullptr; + swapchain->handle->lpVtbl->GetBuffer(swapchain->handle, i, &IID_Resource, (void**)&tmp); + + ImageD3D12* image = &swapchain->images[i]; + image->device = deviceImpl; + image->handle = tmp; + + image->info.belongsToSwapchain = PAL_TRUE; + image->info.depth = 1; // always 1 + image->info.arrayLayerCount = 1; // always 1 + image->info.format = imageFormat; + image->info.usages = PAL_IMAGE_USAGE_COLOR_ATTACHEMENT; + image->info.height = info->height; + image->info.width = info->width; + image->info.mipLevelCount = 1; + image->info.sampleCount = PAL_SAMPLE_COUNT_1; // swapchain images are not multisampled + image->info.type = PAL_IMAGE_TYPE_2D; + } + + // get and cache window size for swapchain out of date error + RECT windowRect; + GetClientRect((HWND)d3dSurface->handle, &windowRect); + swapchain->windowWidth = windowRect.right - windowRect.left; + swapchain->windowWidth = windowRect.bottom - windowRect.top; + + swapchain->device = deviceImpl; + swapchain->queue = queueImpl->handle; + swapchain->imageCount = info->imageCount; + *outSwapchain = (PalSwapchain*)swapchain; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySwapchainD3D12(PalSwapchain* swapchain) +{ + SwapchainD3D12* swapchainImpl = (SwapchainD3D12*)swapchain; + swapchainImpl->handle->lpVtbl->Release(swapchainImpl->handle); + palFree(s_D3D12.allocator, swapchainImpl->images); + palFree(s_D3D12.allocator, swapchainImpl); +} + +PalImage* PAL_CALL getSwapchainImageD3D12( + PalSwapchain* swapchain, + uint32_t index) +{ + SwapchainD3D12* swapchainImpl = (SwapchainD3D12*)swapchain; + if (index > swapchainImpl->imageCount) { + return nullptr; + } + return (PalImage*)&swapchainImpl->images[index]; +} + +PalResult PAL_CALL getNextSwapchainImageD3D12( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex) +{ + HRESULT result; + uint32_t index = 0; + SwapchainD3D12* swapchainImpl = (SwapchainD3D12*)swapchain; + ID3D12CommandQueue* queue = swapchainImpl->queue; + + index = swapchainImpl->handle->lpVtbl->GetCurrentBackBufferIndex(swapchainImpl->handle); + if (info->fence) { + FenceD3D12* fence = (FenceD3D12*)info->fence; + fence->value++; + result = queue->lpVtbl->Signal(queue, fence->handle, fence->value); + if (FAILED(result)) { + pollMessagesD3D12(swapchainImpl->device); + return makeResultD3D12(result); + } + } + + if (info->signalSemaphore) { + SemaphoreD3D12* semaphore = (SemaphoreD3D12*)info->signalSemaphore; + semaphore->value = 1; + result = queue->lpVtbl->Signal(queue, semaphore->handle, semaphore->value); + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + *outIndex = index; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL presentSwapchainD3D12( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore) +{ + HRESULT result; + SwapchainD3D12* swapchainImpl = (SwapchainD3D12*)swapchain; + ID3D12CommandQueue* queue = swapchainImpl->queue; + + if (waitSemaphore) { + SemaphoreD3D12* semaphore = (SemaphoreD3D12*)waitSemaphore; + result = queue->lpVtbl->Wait(queue, semaphore->handle, semaphore->value); + if (FAILED(result)) { + return makeResultD3D12(result); + } + + semaphore->value = 0; + result = semaphore->handle->lpVtbl->Signal(semaphore->handle, 0); + if (FAILED(result)) { + return makeResultD3D12(result); + } + } + + // poll pending messages + pollMessagesD3D12(swapchainImpl->device); + + result = swapchainImpl->handle->lpVtbl->Present( + swapchainImpl->handle, + swapchainImpl->syncInterval, + swapchainImpl->presentFlags); + + pollMessagesD3D12(swapchainImpl->device); + if (FAILED(result)) { + if (result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET) { + pollMessagesD3D12(swapchainImpl->device); + return makeResultD3D12(result); + } + + // check if swapchain needs to be resize + RECT windowRect; + PalBool ret = GetClientRect((HWND)swapchainImpl->surface->handle, &windowRect); + uint32_t w = windowRect.right - windowRect.left; + uint32_t h = windowRect.bottom - windowRect.top; + if (!ret) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + if (w != swapchainImpl->windowWidth || h != swapchainImpl->windowHeight) { + return PAL_RESULT_CODE_OUT_OF_DATE; + } + + return makeResultD3D12(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL resizeSwapchainD3D12( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight) +{ + HRESULT result; + SwapchainD3D12* swapchainImpl = (SwapchainD3D12*)swapchain; + result = swapchainImpl->handle->lpVtbl->ResizeBuffers( + swapchainImpl->handle, + swapchainImpl->imageCount, + newWidth, + newHeight, + swapchainImpl->format, + swapchainImpl->flags); + + if (FAILED(result)) { + pollMessagesD3D12(swapchainImpl->device); + return makeResultD3D12(result); + } + + // fill all images with the creation info + for (int i = 0; i < swapchainImpl->imageCount; i++) { + ID3D12Resource* tmp = nullptr; + swapchainImpl->handle->lpVtbl + ->GetBuffer(swapchainImpl->handle, i, &IID_Resource, (void**)&tmp); + + ImageD3D12* image = &swapchainImpl->images[i]; + image->handle = tmp; + image->info.height = newHeight; + image->info.width = newWidth; + } + + // get and cache window size for swapchain out of date error + RECT windowRect; + SurfaceD3D12* surface = swapchainImpl->surface; + if (!GetClientRect((HWND)surface->handle, &windowRect)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + swapchainImpl->windowWidth = windowRect.right - windowRect.left; + swapchainImpl->windowWidth = windowRect.bottom - windowRect.top; + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/d3d12/pal_sync_d3d12.c b/src/graphics/d3d12/pal_sync_d3d12.c new file mode 100644 index 00000000..1822bb9e --- /dev/null +++ b/src/graphics/d3d12/pal_sync_d3d12.c @@ -0,0 +1,217 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_D3D12_BACKEND +#include "pal_d3d12.h" + +PalResult PAL_CALL createFenceD3D12( + PalDevice* device, + PalBool signaled, + PalFence** outFence) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + FenceD3D12* fence = nullptr; + + fence = palAllocate(s_D3D12.allocator, sizeof(FenceD3D12), 0); + if (!fence) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + result = deviceImpl->handle->lpVtbl + ->CreateFence(deviceImpl->handle, 0, 0, &IID_Fence, (void**)&fence->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + palFree(s_D3D12.allocator, fence); + return makeResultD3D12(result); + } + + // create event + fence->event = CreateEvent(nullptr, PAL_FALSE, PAL_FALSE, nullptr); + if (!fence->event) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + fence->canReset = PAL_FALSE; + if (deviceImpl->canFenceReset) { + fence->canReset = PAL_TRUE; + } + + fence->isTimeline = PAL_FALSE; // for sempaphores + fence->value = 0; + *outFence = (PalFence*)fence; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyFenceD3D12(PalFence* fence) +{ + FenceD3D12* fenceImpl = (FenceD3D12*)fence; + fenceImpl->handle->lpVtbl->Release(fenceImpl->handle); + CloseHandle(fenceImpl->event); + palFree(s_D3D12.allocator, fenceImpl); +} + +PalResult PAL_CALL waitFenceD3D12( + PalFence* fence, + uint64_t timeout) +{ + HRESULT result; + FenceD3D12* fenceImpl = (FenceD3D12*)fence; + DWORD ret = 0; + uint64_t value = fenceImpl->value; + HANDLE event = fenceImpl->event; + + if (fenceImpl->handle->lpVtbl->GetCompletedValue(fenceImpl->handle) < value) { + result = fenceImpl->handle->lpVtbl->SetEventOnCompletion(fenceImpl->handle, value, event); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + if (timeout == PAL_INFINITE) { + ret = WaitForSingleObject(event, INFINITE); + } else { + ret = WaitForSingleObject(event, (DWORD)timeout); + } + } + + if (ret == WAIT_TIMEOUT) { + return PAL_RESULT_CODE_TIMEOUT; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL resetFenceD3D12(PalFence* fence) +{ + FenceD3D12* fenceImpl = (FenceD3D12*)fence; + fenceImpl->handle->lpVtbl->Signal(fenceImpl->handle, 0); + fenceImpl->value = 0; + return PAL_RESULT_SUCCESS; +} + +PalBool PAL_CALL isFenceSignaledD3D12(PalFence* fence) +{ + FenceD3D12* fenceImpl = (FenceD3D12*)fence; + if (fenceImpl->handle->lpVtbl->GetCompletedValue(fenceImpl->handle) == 0) { + return PAL_FALSE; + } + return PAL_TRUE; +} + +PalResult PAL_CALL createSemaphoreD3D12( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore) +{ + HRESULT result; + DeviceD3D12* deviceImpl = (DeviceD3D12*)device; + SemaphoreD3D12* semaphore = nullptr; + + semaphore = palAllocate(s_D3D12.allocator, sizeof(SemaphoreD3D12), 0); + if (!semaphore) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (enableTimeline) { + semaphore->isTimeline = PAL_TRUE; + } + + result = deviceImpl->handle->lpVtbl + ->CreateFence(deviceImpl->handle, 0, 0, &IID_Fence, (void**)&semaphore->handle); + + if (FAILED(result)) { + pollMessagesD3D12(deviceImpl); + palFree(s_D3D12.allocator, semaphore); + return makeResultD3D12(result); + } + + // create event + semaphore->event = CreateEvent(nullptr, PAL_FALSE, PAL_FALSE, nullptr); + if (!semaphore->event) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + semaphore->canReset = PAL_FALSE; + semaphore->value = 0; + *outSemaphore = (PalSemaphore*)semaphore; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySemaphoreD3D12(PalSemaphore* semaphore) +{ + SemaphoreD3D12* semaphoreImpl = (SemaphoreD3D12*)semaphore; + semaphoreImpl->handle->lpVtbl->Release(semaphoreImpl->handle); + CloseHandle(semaphoreImpl->event); + palFree(s_D3D12.allocator, semaphoreImpl); +} + +PalResult PAL_CALL waitSemaphoreD3D12( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout) +{ + HRESULT result; + DWORD ret = 0; + SemaphoreD3D12* semaphoreImpl = (SemaphoreD3D12*)semaphore; + + HANDLE event = semaphoreImpl->event; + if (semaphoreImpl->handle->lpVtbl->GetCompletedValue(semaphoreImpl->handle) < value) { + result = semaphoreImpl->handle->lpVtbl->SetEventOnCompletion( + semaphoreImpl->handle, + value, + event); + + if (FAILED(result)) { + return makeResultD3D12(result); + } + + if (timeout == PAL_INFINITE) { + ret = WaitForSingleObject(event, INFINITE); + } else { + ret = WaitForSingleObject(event, (DWORD)timeout); + } + } + + if (ret == WAIT_TIMEOUT) { + return PAL_RESULT_CODE_TIMEOUT; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL signalSemaphoreD3D12( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value) +{ + SemaphoreD3D12* semaphoreImpl = (SemaphoreD3D12*)semaphore; + HRESULT result = semaphoreImpl->handle->lpVtbl->Signal(semaphoreImpl->handle, value); + if (FAILED(result)) { + return makeResultD3D12(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL getSemaphoreValueD3D12( + PalSemaphore* semaphore, + uint64_t* value) +{ + SemaphoreD3D12* semaphoreImpl = (SemaphoreD3D12*)semaphore; + *value = semaphoreImpl->handle->lpVtbl->GetCompletedValue(semaphoreImpl->handle); + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_D3D12_BACKEND \ No newline at end of file diff --git a/src/graphics/pal_graphics.c b/src/graphics/pal_graphics.c new file mode 100644 index 00000000..6331d117 --- /dev/null +++ b/src/graphics/pal_graphics.c @@ -0,0 +1,1759 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_graphics_backends.h" + +#define ceil(a, b) (a + b - 1) / b +#define min(a, b) (a < b) ? a : b + +#define MAX_BACKENDS (PAL_MAX_CUSTOM_BACKENDS + 2) +#define PAL_HANDLE(name) \ + struct name { \ + PalGraphicsVtable backend; \ + }; + +PAL_HANDLE(PalAdapter) +PAL_HANDLE(PalDevice) +PAL_HANDLE(PalQueue) +PAL_HANDLE(PalMemory) +PAL_HANDLE(PalSwapchain) +PAL_HANDLE(PalImage) +PAL_HANDLE(PalImageView) +PAL_HANDLE(PalShader) +PAL_HANDLE(PalBuffer) + +PAL_HANDLE(PalFence) +PAL_HANDLE(PalSemaphore) +PAL_HANDLE(PalCommandPool) +PAL_HANDLE(PalCommandBuffer) +PAL_HANDLE(PalPipeline) +PAL_HANDLE(PalAccelerationStructure) +PAL_HANDLE(PalPipelineLayout) +PAL_HANDLE(PalDescriptorSetLayout) +PAL_HANDLE(PalDescriptorPool) +PAL_HANDLE(PalDescriptorSet) +PAL_HANDLE(PalSampler) +PAL_HANDLE(PalSurface) +PAL_HANDLE(PalShaderBindingTable) + +typedef struct { + int32_t count; + int32_t startIndex; + PalGraphicsVtable base; +} BackendData; + +typedef struct { + int32_t backendCount; + const PalAllocator* allocator; + BackendData backends[MAX_BACKENDS]; +} Graphics; + +static Graphics s_Graphics = {0}; + +static PalBool validateVtableVersion1(const PalGraphicsBackendVtable1* vtable1) +{ + // validate required functions + // clang-format off + if (!vtable1->enumerateAdapters || + !vtable1->getAdapterInfo || + !vtable1->getAdapterCapabilities || + !vtable1->getAdapterFeatures || + !vtable1->getHighestSupportedShaderTarget || + + // device + !vtable1->createDevice || + !vtable1->destroyDevice || + !vtable1->getDeviceLostReason || + + // memory + !vtable1->allocateMemory || + !vtable1->freeMemory || + + // queue + !vtable1->createQueue || + !vtable1->destroyQueue || + !vtable1->waitQueue || + !vtable1->canQueuePresent || + + // formats + !vtable1->enumerateFormats || + !vtable1->isFormatSupported || + !vtable1->queryFormatImageUsages || + !vtable1->queryFormatSampleCount || + + // image + !vtable1->createImage || + !vtable1->destroyImage || + !vtable1->getImageInfo || + !vtable1->getImageMemoryRequirements || + !vtable1->bindImageMemory || + + // image view + !vtable1->createImageView || + !vtable1->destroyImageView || + + // sampler + !vtable1->createSampler || + !vtable1->destroySampler || + + // shader + !vtable1->createShader || + !vtable1->destroyShader || + + // fence + !vtable1->createFence || + !vtable1->destroyFence || + !vtable1->waitFence || + !vtable1->isFenceSignaled || + + // semaphore + !vtable1->createSemaphore || + !vtable1->destroySemaphore || + + // command pool and command buffer + !vtable1->createCommandPool || + !vtable1->destroyCommandPool || + !vtable1->allocateCommandBuffer || + !vtable1->freeCommandBuffer || + !vtable1->resetCommandBuffer || + !vtable1->submitCommandBuffer || + + // command recording + !vtable1->cmdBegin || + !vtable1->cmdEnd || + !vtable1->cmdExecuteCommandBuffer || + !vtable1->cmdBeginRendering || + !vtable1->cmdEndRendering || + !vtable1->cmdCopyBuffer || + !vtable1->cmdCopyBufferToImage || + !vtable1->cmdCopyImage || + !vtable1->cmdCopyImageToBuffer || + !vtable1->cmdBindPipeline || + !vtable1->cmdSetViewport || + !vtable1->cmdSetScissors || + !vtable1->cmdBindVertexBuffers || + !vtable1->cmdBindIndexBuffer || + !vtable1->cmdDraw || + !vtable1->cmdDrawIndexed || + !vtable1->cmdImageBarrier || + !vtable1->cmdBufferBarrier || + !vtable1->cmdDispatch || + !vtable1->cmdBindDescriptorSet || + !vtable1->cmdPushConstants || + + // buffer + !vtable1->createBuffer || + !vtable1->destroyBuffer || + !vtable1->getBufferMemoryRequirements || + !vtable1->computeImageStagingRequirements || + !vtable1->writeImageStaging || + !vtable1->bindBufferMemory || + !vtable1->mapBuffer || + !vtable1->unmapBuffer || + + // descriptors + !vtable1->createDescriptorSetLayout || + !vtable1->destroyDescriptorSetLayout || + !vtable1->createDescriptorPool || + !vtable1->destroyDescriptorPool || + !vtable1->resetDescriptorPool || + !vtable1->allocateDescriptorSet || + !vtable1->updateDescriptorSet || + + // pipeline layout + !vtable1->createPipelineLayout || + !vtable1->destroyPipelineLayout || + + // pipeline + !vtable1->createGraphicsPipeline || + !vtable1->createComputePipeline || + !vtable1->destroyPipeline) { + return PAL_FALSE; + } + // clang-format on + return PAL_TRUE; +} + +static PalBool addBackend(const PalGraphicsBackendInfo* backendInfo) +{ + BackendData* backendData = &s_Graphics.backends[s_Graphics.backendCount++]; + backendData->startIndex = 0; + backendData->count = 0; + + // populate our internal vtable + if (backendInfo->version == PAL_GRAPHICS_BACKEND_VTABLE_VERSION_1) { + // validate that all version 1 required pointers are set + const PalGraphicsBackendVtable1* vtable1 = (PalGraphicsBackendVtable1*)backendInfo->vtable; + if (!validateVtableVersion1(vtable1)) { + return PAL_FALSE; + } + memset(&backendData->base, 0, sizeof(PalGraphicsVtable)); + backendData->base.vtbl1 = vtable1; + } + + return PAL_TRUE; +} + +PalResult PAL_CALL palInitGraphics( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator, + uint32_t customBackendCount, + const PalGraphicsBackendInfo* customBackends) +{ + PalResult result; + BackendData* attachedBackend = nullptr; +#ifdef _WIN32 +#if PAL_HAS_D3D12_BACKEND + result = initGraphicsD3D12(debugger, allocator); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + attachedBackend = &s_Graphics.backends[s_Graphics.backendCount++]; + attachedBackend->base.vtbl1 = &s_D3D12Backend1; + attachedBackend->startIndex = 0; + attachedBackend->count = 0; +#endif // PAL_HAS_D3D12_BACKEND + +#if PAL_HAS_VULKAN_BACKEND + result = initGraphicsVk(debugger, allocator); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + attachedBackend = &s_Graphics.backends[s_Graphics.backendCount++]; + attachedBackend->base.vtbl1 = &s_VkBackend1; + attachedBackend->startIndex = 0; + attachedBackend->count = 0; +#endif // PAL_HAS_VULKAN_BACKEND + +#elif defined(__linux__) + // vulkan +#if PAL_HAS_VULKAN_BACKEND + result = initGraphicsVk(debugger, allocator); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + attachedBackend = &s_Graphics.backends[s_Graphics.backendCount++]; + attachedBackend->base.vtbl1 = &s_VkBackend1; + attachedBackend->startIndex = 0; + attachedBackend->count = 0; +#endif // PAL_HAS_VULKAN_BACKEND +#else + // metal or andriod +#endif // _WIN32 + + // custom backends + for (uint32_t i = 0; i < customBackendCount; i++) { + if (!addBackend(&customBackends[i])) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + s_Graphics.allocator = allocator; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palShutdownGraphics() +{ +#ifdef _WIN32 +#if PAL_HAS_D3D12_BACKEND + shutdownGraphicsD3D12(); +#endif // PAL_HAS_D3D12_BACKEND + +#if PAL_HAS_VULKAN_BACKEND + shutdownGraphicsVk(); +#endif // PAL_HAS_VULKAN_BACKEND + +#elif defined(__linux__) + // vulkan +#if PAL_HAS_VULKAN_BACKEND + shutdownGraphicsVk(); +#endif // PAL_HAS_VULKAN_BACKEND + +#else + // metal or andriod +#endif // _WIN32 + + memset(&s_Graphics, 0, sizeof(s_Graphics)); +} + +// ================================================== +// Adapter +// ================================================== + +PalResult PAL_CALL palEnumerateAdapters( + uint32_t* count, + PalAdapter** outAdapters) +{ + if (!count) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + // enumerate all adapters for both custom and PAL backends + PalResult result = 0; + int totalCount = 0; + int index = 0; + uint32_t _count = 0; + + for (int i = 0; i < s_Graphics.backendCount; i++) { + BackendData* backend = &s_Graphics.backends[i]; + if (outAdapters) { + // offset into the array so all backends write at the correct index + PalAdapter** adapters = &outAdapters[backend->startIndex]; + _count = backend->count; + result = backend->base.vtbl1->enumerateAdapters(&_count, adapters); + // break if a backend fails + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + for (int j = 0; j < _count; j++) { + PalAdapter* tmp = adapters[j]; + tmp->backend.vtbl1 = backend->base.vtbl1; + } + + } else { + result = backend->base.vtbl1->enumerateAdapters(&_count, nullptr); + // break if a backend fails + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + backend->startIndex = totalCount; + backend->count = _count; + totalCount += _count; + _count = 0; + } + } + + if (!outAdapters) { + *count = totalCount; + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palGetAdapterInfo( + PalAdapter* adapter, + PalAdapterInfo* info) +{ + adapter->backend.vtbl1->getAdapterInfo(adapter, info); +} + +void PAL_CALL palGetAdapterCapabilities( + PalAdapter* adapter, + PalAdapterCapabilities* caps) +{ + adapter->backend.vtbl1->getAdapterCapabilities(adapter, caps); +} + +PalAdapterFeatures PAL_CALL palGetAdapterFeatures(PalAdapter* adapter) +{ + return adapter->backend.vtbl1->getAdapterFeatures(adapter); +} + +uint32_t PAL_CALL palGetHighestSupportedShaderTarget( + PalAdapter* adapter, + PalShaderFormats shaderFormat) +{ + return adapter->backend.vtbl1->getHighestSupportedShaderTarget(adapter, shaderFormat); +} + +// ================================================== +// Device +// ================================================== + +PalResult PAL_CALL palCreateDevice( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice) +{ + if (!adapter || !outDevice) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalDevice* device = nullptr; + PalResult result; + result = adapter->backend.vtbl1->createDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + device->backend = adapter->backend; + *outDevice = device; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyDevice(PalDevice* device) +{ + device->backend.vtbl1->destroyDevice(device); +} + +uint32_t PAL_CALL palGetDeviceLostReason(PalDevice* device) +{ + return (uint32_t)device->backend.vtbl1->getDeviceLostReason(device); +} + +PalResult PAL_CALL palAllocateMemory( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory) +{ + if (!device || !outMemory) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalMemory* memory = nullptr; + PalResult result; + result = device->backend.vtbl1->allocateMemory(device, type, memoryMask, size, &memory); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + memory->backend = device->backend; + *outMemory = memory; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palFreeMemory(PalMemory* memory) +{ + memory->backend.vtbl1->freeMemory(memory); +} + +// ================================================== +// Extended Adapter Features +// ================================================== + +void PAL_CALL palQuerySamplerAnisotropyCapabilities( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps) +{ + device->backend.vtbl1->querySamplerAnisotropyCapabilities(device, caps); +} + +void PAL_CALL palQueryMultiViewCapabilities( + PalDevice* device, + PalMultiViewCapabilities* caps) +{ + device->backend.vtbl1->queryMultiViewCapabilities(device, caps); +} + +void PAL_CALL palQueryMultiViewportCapabilities( + PalDevice* device, + PalMultiViewportCapabilities* caps) +{ + device->backend.vtbl1->queryMultiViewportCapabilities(device, caps); +} + +void PAL_CALL palQueryDepthStencilCapabilities( + PalDevice* device, + PalDepthStencilCapabilities* caps) +{ + device->backend.vtbl1->queryDepthStencilCapabilities(device, caps); +} + +void PAL_CALL palQueryFragmentShadingRateCapabilities( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps) +{ + device->backend.vtbl1->queryFragmentShadingRateCapabilities(device, caps); +} + +void PAL_CALL palQueryMeshShaderCapabilities( + PalDevice* device, + PalMeshShaderCapabilities* caps) +{ + device->backend.vtbl1->queryMeshShaderCapabilities(device, caps); +} + +void PAL_CALL palQueryRayTracingCapabilities( + PalDevice* device, + PalRayTracingCapabilities* caps) +{ + device->backend.vtbl1->queryRayTracingCapabilities(device, caps); +} + +void PAL_CALL palQueryDescriptorIndexingCapabilities( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps) +{ + device->backend.vtbl1->queryDescriptorIndexingCapabilities(device, caps); +} + +// ================================================== +// Queue +// ================================================== + +PalResult PAL_CALL palCreateQueue( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue) +{ + if (!device || !outQueue) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalQueue* queue = nullptr; + PalResult result; + result = device->backend.vtbl1->createQueue(device, type, &queue); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + queue->backend = device->backend; + *outQueue = queue; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyQueue(PalQueue* queue) +{ + queue->backend.vtbl1->destroyQueue(queue); +} + +PalBool PAL_CALL palCanQueuePresent( + PalQueue* queue, + PalSurface* surface) +{ + return queue->backend.vtbl1->canQueuePresent(queue, surface); +} + +PalResult PAL_CALL palWaitQueue(PalQueue* queue) +{ + return queue->backend.vtbl1->waitQueue(queue); +} + +// ================================================== +// Format And Usages +// ================================================== + +void PAL_CALL palEnumerateFormats( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats) +{ + adapter->backend.vtbl1->enumerateFormats(adapter, count, outFormats); +} + +PalBool PAL_CALL palIsFormatSupported( + PalAdapter* adapter, + PalFormat format) +{ + return adapter->backend.vtbl1->isFormatSupported(adapter, format); +} + +PalImageUsages PAL_CALL palQueryFormatImageUsages( + PalAdapter* adapter, + PalFormat format) +{ + return adapter->backend.vtbl1->queryFormatImageUsages(adapter, format); +} + +PalSampleCount PAL_CALL palQueryFormatSampleCount( + PalAdapter* adapter, + PalFormat format) +{ + return adapter->backend.vtbl1->queryFormatSampleCount(adapter, format); +} + +// ================================================== +// Image +// ================================================== + +PalResult PAL_CALL palCreateImage( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage) +{ + if (!device || !info || !outImage) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalImage* image = nullptr; + PalResult result; + result = device->backend.vtbl1->createImage(device, info, &image); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + image->backend = device->backend; + *outImage = image; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyImage(PalImage* image) +{ + image->backend.vtbl1->destroyImage(image); +} + +void PAL_CALL palGetImageInfo( + PalImage* image, + PalImageInfo* info) +{ + image->backend.vtbl1->getImageInfo(image, info); +} + +void PAL_CALL palGetImageMemoryRequirements( + PalImage* image, + PalMemoryRequirements* requirements) +{ + image->backend.vtbl1->getImageMemoryRequirements(image, requirements); +} + +PalResult PAL_CALL palBindImageMemory( + PalImage* image, + PalMemory* memory, + uint64_t offset) +{ + return image->backend.vtbl1->bindImageMemory(image, memory, offset); +} + +// ================================================== +// Image View +// ================================================== + +PalResult PAL_CALL palCreateImageView( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView) +{ + if (!device || !image || !info || !outImageView) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalImageView* imageView = nullptr; + PalResult result; + result = device->backend.vtbl1->createImageView(device, image, info, &imageView); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + imageView->backend = device->backend; + *outImageView = imageView; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyImageView(PalImageView* imageView) +{ + imageView->backend.vtbl1->destroyImageView(imageView); +} + +// ================================================== +// Sampler +// ================================================== + +PalResult PAL_CALL palCreateSampler( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler) +{ + if (!device || !info || !outSampler) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalSampler* sampler = nullptr; + PalResult result; + result = device->backend.vtbl1->createSampler(device, info, &sampler); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + sampler->backend = device->backend; + *outSampler = sampler; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroySampler(PalSampler* sampler) +{ + sampler->backend.vtbl1->destroySampler(sampler); +} + +// ================================================== +// Surface +// ================================================== + +PalResult PAL_CALL palCreateSurface( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface) +{ + if (!device || !window || !windowInstance || !outSurface) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalSurface* surface = nullptr; + PalResult ret; + ret = device->backend.vtbl1 + ->createSurface(device, window, windowInstance, instanceType, &surface); + if (ret != PAL_RESULT_SUCCESS) { + return ret; + } + + surface->backend = device->backend; + *outSurface = surface; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroySurface(PalSurface* surface) +{ + surface->backend.vtbl1->destroySurface(surface); +} + +void PAL_CALL palGetSurfaceCapabilities( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps) +{ + device->backend.vtbl1->getSurfaceCapabilities(device, surface, caps); +} + +// ================================================== +// Swapchain +// ================================================== + +PalResult PAL_CALL palCreateSwapchain( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain) +{ + if (!device || !queue || !surface || !info || !outSwapchain) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalSwapchain* swapchain = nullptr; + result = device->backend.vtbl1->createSwapchain(device, queue, surface, info, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + // set the backend for all swapchain images + for (int i = 0; i < info->imageCount; i++) { + PalImage* image = device->backend.vtbl1->getSwapchainImage(swapchain, i); + image->backend = device->backend; + } + + swapchain->backend = device->backend; + *outSwapchain = swapchain; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroySwapchain(PalSwapchain* swapchain) +{ + swapchain->backend.vtbl1->destroySwapchain(swapchain); +} + +PalImage* PAL_CALL palGetSwapchainImage( + PalSwapchain* swapchain, + uint32_t index) +{ + return swapchain->backend.vtbl1->getSwapchainImage(swapchain, index); +} + +PalResult PAL_CALL palGetNextSwapchainImage( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex) +{ + return swapchain->backend.vtbl1->getNextSwapchainImage(swapchain, info, outIndex); +} + +PalResult PAL_CALL palPresentSwapchain( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore) +{ + return swapchain->backend.vtbl1->presentSwapchain(swapchain, imageIndex, waitSemaphore); +} + +PalResult PAL_CALL palResizeSwapchain( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight) +{ + return swapchain->backend.vtbl1->resizeSwapchain(swapchain, newWidth, newHeight); +} + +// ================================================== +// Shader +// ================================================== + +PalResult PAL_CALL palCreateShader( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader) +{ + if (!device || !info || !outShader) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalShader* shader = nullptr; + PalResult result; + result = device->backend.vtbl1->createShader(device, info, &shader); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + shader->backend = device->backend; + *outShader = shader; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyShader(PalShader* shader) +{ + shader->backend.vtbl1->destroyShader(shader); +} + +// ================================================== +// Fence +// ================================================== + +PalResult PAL_CALL palCreateFence( + PalDevice* device, + PalBool signaled, + PalFence** outFence) +{ + if (!device || !outFence) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalFence* fence = nullptr; + PalResult result; + result = device->backend.vtbl1->createFence(device, signaled, &fence); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + fence->backend = device->backend; + *outFence = fence; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyFence(PalFence* fence) +{ + fence->backend.vtbl1->destroyFence(fence); +} + +PalResult PAL_CALL palWaitFence( + PalFence* fence, + uint64_t timeout) +{ + return fence->backend.vtbl1->waitFence(fence, timeout); +} + +PalResult PAL_CALL palResetFence(PalFence* fence) +{ + return fence->backend.vtbl1->resetFence(fence); +} + +PalBool PAL_CALL palIsFenceSignaled(PalFence* fence) +{ + return fence->backend.vtbl1->isFenceSignaled(fence); +} + +// ================================================== +// Semaphore +// ================================================== + +PalResult PAL_CALL palCreateSemaphore( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore) +{ + if (!device || !outSemaphore) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalSemaphore* semaphore = nullptr; + PalResult result; + result = device->backend.vtbl1->createSemaphore(device, enableTimeline, &semaphore); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + semaphore->backend = device->backend; + *outSemaphore = semaphore; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroySemaphore(PalSemaphore* semaphore) +{ + semaphore->backend.vtbl1->destroySemaphore(semaphore); +} + +PalResult PAL_CALL palWaitSemaphore( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout) +{ + return semaphore->backend.vtbl1->waitSemaphore(semaphore, value, timeout); +} + +PalResult PAL_CALL palSignalSemaphore( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value) +{ + return semaphore->backend.vtbl1->signalSemaphore(semaphore, queue, value); +} + +PalResult PAL_CALL palGetSemaphoreValue( + PalSemaphore* semaphore, + uint64_t* value) +{ + return semaphore->backend.vtbl1->getSemaphoreValue(semaphore, value); +} + +// ================================================== +// Command Pool And Buffer +// ================================================== + +PalResult PAL_CALL palCreateCommandPool( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool) +{ + if (!device || !queue || !outPool) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalCommandPool* pool = nullptr; + PalResult result; + result = device->backend.vtbl1->createCommandPool(device, queue, &pool); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + pool->backend = device->backend; + *outPool = pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyCommandPool(PalCommandPool* pool) +{ + pool->backend.vtbl1->destroyCommandPool(pool); +} + +PalResult PAL_CALL palAllocateCommandBuffer( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer) +{ + if (!device || !pool || !outCmdBuffer) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalCommandBuffer* cmdBuffer = nullptr; + PalResult result; + result = device->backend.vtbl1->allocateCommandBuffer(device, pool, type, &cmdBuffer); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + cmdBuffer->backend = device->backend; + *outCmdBuffer = cmdBuffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palFreeCommandBuffer(PalCommandBuffer* cmdBuffer) +{ + cmdBuffer->backend.vtbl1->freeCommandBuffer(cmdBuffer); +} + +PalResult PAL_CALL palResetCommandBuffer(PalCommandBuffer* cmdBuffer) +{ + return cmdBuffer->backend.vtbl1->resetCommandBuffer(cmdBuffer); +} + +PalResult PAL_CALL palSubmitCommandBuffer( + PalQueue* queue, + PalCommandBufferSubmitInfo* info) +{ + return queue->backend.vtbl1->submitCommandBuffer(queue, info); +} + +// ================================================== +// Command Recording +// ================================================== + +PalResult PAL_CALL palCmdBegin( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info) +{ + return cmdBuffer->backend.vtbl1->cmdBegin(cmdBuffer, info); +} + +PalResult PAL_CALL palCmdEnd(PalCommandBuffer* cmdBuffer) +{ + return cmdBuffer->backend.vtbl1->cmdEnd(cmdBuffer); +} + +void PAL_CALL palCmdExecuteCommandBuffer( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer) +{ + primaryCmdBuffer->backend.vtbl1->cmdExecuteCommandBuffer(primaryCmdBuffer, secondaryCmdBuffer); +} + +void PAL_CALL palCmdSetFragmentShadingRate( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state) +{ + cmdBuffer->backend.vtbl1->cmdSetFragmentShadingRate(cmdBuffer, state); +} + +void PAL_CALL palCmdDrawMeshTasks( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + cmdBuffer->backend.vtbl1->cmdDrawMeshTasks(cmdBuffer, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL palCmdDrawMeshTasksIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount) +{ + cmdBuffer->backend.vtbl1->cmdDrawMeshTasksIndirect(cmdBuffer, buffer, drawCount); +} + +void PAL_CALL palCmdDrawMeshTasksIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + cmdBuffer->backend.vtbl1 + ->cmdDrawMeshTasksIndirectCount(cmdBuffer, buffer, countBuffer, maxDrawCount); +} + +void PAL_CALL palCmdBuildAccelerationStructure( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info) +{ + cmdBuffer->backend.vtbl1->cmdBuildAccelerationStructure(cmdBuffer, info); +} + +void PAL_CALL palCmdBeginRendering( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info) +{ + cmdBuffer->backend.vtbl1->cmdBeginRendering(cmdBuffer, info); +} + +void PAL_CALL palCmdEndRendering(PalCommandBuffer* cmdBuffer) +{ + cmdBuffer->backend.vtbl1->cmdEndRendering(cmdBuffer); +} + +void PAL_CALL palCmdCopyBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo) +{ + cmdBuffer->backend.vtbl1->cmdCopyBuffer(cmdBuffer, dst, src, copyInfo); +} + +void PAL_CALL palCmdCopyBufferToImage( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo) +{ + cmdBuffer->backend.vtbl1->cmdCopyBufferToImage(cmdBuffer, dstImage, srcBuffer, copyInfo); +} + +void PAL_CALL palCmdCopyImage( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo) +{ + cmdBuffer->backend.vtbl1->cmdCopyImage(cmdBuffer, dst, src, copyInfo); +} + +void PAL_CALL palCmdCopyImageToBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo) +{ + cmdBuffer->backend.vtbl1->cmdCopyImageToBuffer(cmdBuffer, dstBuffer, srcImage, copyInfo); +} + +void PAL_CALL palCmdBindPipeline( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline) +{ + cmdBuffer->backend.vtbl1->cmdBindPipeline(cmdBuffer, pipeline); +} + +void PAL_CALL palCmdSetViewport( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports) +{ + cmdBuffer->backend.vtbl1->cmdSetViewport(cmdBuffer, count, viewports); +} + +void PAL_CALL palCmdSetScissors( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors) +{ + cmdBuffer->backend.vtbl1->cmdSetScissors(cmdBuffer, count, scissors); +} + +void PAL_CALL palCmdBindVertexBuffers( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets) +{ + cmdBuffer->backend.vtbl1->cmdBindVertexBuffers(cmdBuffer, firstSlot, count, buffers, offsets); +} + +void PAL_CALL palCmdBindIndexBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type) +{ + cmdBuffer->backend.vtbl1->cmdBindIndexBuffer(cmdBuffer, buffer, offset, type); +} + +void PAL_CALL palCmdDraw( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) +{ + cmdBuffer->backend.vtbl1 + ->cmdDraw(cmdBuffer, vertexCount, instanceCount, firstVertex, firstInstance); +} + +void PAL_CALL palCmdDrawIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + cmdBuffer->backend.vtbl1->cmdDrawIndirect(cmdBuffer, buffer, count); +} + +void PAL_CALL palCmdDrawIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + cmdBuffer->backend.vtbl1->cmdDrawIndirectCount(cmdBuffer, buffer, countBuffer, maxDrawCount); +} + +void PAL_CALL palCmdDrawIndexed( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance) +{ + cmdBuffer->backend.vtbl1->cmdDrawIndexed( + cmdBuffer, + indexCount, + instanceCount, + firstIndex, + vertexOffset, + firstInstance); +} + +void PAL_CALL palCmdDrawIndexedIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + cmdBuffer->backend.vtbl1->cmdDrawIndexedIndirect(cmdBuffer, buffer, count); +} + +void PAL_CALL palCmdDrawIndexedIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + cmdBuffer->backend.vtbl1 + ->cmdDrawIndexedIndirectCount(cmdBuffer, buffer, countBuffer, maxDrawCount); +} + +void PAL_CALL palCmdAccelerationStructureBarrier( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info) +{ + cmdBuffer->backend.vtbl1->cmdAccelerationStructureBarrier(cmdBuffer, as, info); +} + +void PAL_CALL palCmdImageBarrier( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info) +{ + cmdBuffer->backend.vtbl1->cmdImageBarrier(cmdBuffer, image, subresourceRange, info); +} + +void PAL_CALL palCmdBufferBarrier( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info) +{ + cmdBuffer->backend.vtbl1->cmdBufferBarrier(cmdBuffer, buffer, info); +} + +void PAL_CALL palCmdDispatch( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + cmdBuffer->backend.vtbl1->cmdDispatch(cmdBuffer, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL palCmdDispatchBase( + PalCommandBuffer* cmdBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + cmdBuffer->backend.vtbl1->cmdDispatchBase( + cmdBuffer, + baseGroupX, + baseGroupY, + baseGroupZ, + groupCountX, + groupCountY, + groupCountZ); +} + +void PAL_CALL palCmdDispatchIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer) +{ + cmdBuffer->backend.vtbl1->cmdDispatchIndirect(cmdBuffer, buffer); +} + +void PAL_CALL palCmdTraceRays( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth) +{ + cmdBuffer->backend.vtbl1->cmdTraceRays(cmdBuffer, sbt, raygenIndex, width, height, depth); +} + +void PAL_CALL palCmdTraceRaysIndirect( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer) +{ + cmdBuffer->backend.vtbl1->cmdTraceRaysIndirect(cmdBuffer, raygenIndex, sbt, buffer); +} + +void PAL_CALL palCmdBindDescriptorSet( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set) +{ + cmdBuffer->backend.vtbl1->cmdBindDescriptorSet(cmdBuffer, setIndex, set); +} + +void PAL_CALL palCmdPushConstants( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value) +{ + cmdBuffer->backend.vtbl1->cmdPushConstants(cmdBuffer, offset, size, value); +} + +void PAL_CALL palCmdSetCullMode( + PalCommandBuffer* cmdBuffer, + PalCullMode cullMode) +{ + cmdBuffer->backend.vtbl1->cmdSetCullMode(cmdBuffer, cullMode); +} + +void PAL_CALL palCmdSetFrontFace( + PalCommandBuffer* cmdBuffer, + PalFrontFace frontFace) +{ + cmdBuffer->backend.vtbl1->cmdSetFrontFace(cmdBuffer, frontFace); +} + +void PAL_CALL palCmdSetPrimitiveTopology( + PalCommandBuffer* cmdBuffer, + PalPrimitiveTopology topology) +{ + cmdBuffer->backend.vtbl1->cmdSetPrimitiveTopology(cmdBuffer, topology); +} + +void PAL_CALL palCmdSetDepthTestEnable( + PalCommandBuffer* cmdBuffer, + PalBool enable) +{ + cmdBuffer->backend.vtbl1->cmdSetDepthTestEnable(cmdBuffer, enable); +} + +void PAL_CALL palCmdSetDepthWriteEnable( + PalCommandBuffer* cmdBuffer, + PalBool enable) +{ + cmdBuffer->backend.vtbl1->cmdSetDepthWriteEnable(cmdBuffer, enable); +} + +void PAL_CALL palCmdSetStencilOp( + PalCommandBuffer* cmdBuffer, + PalStencilFaceFlags faceMask, + PalStencilOp failOp, + PalStencilOp passOp, + PalStencilOp depthFailOp, + PalCompareOp compareOp) +{ + cmdBuffer->backend.vtbl1 + ->cmdSetStencilOp(cmdBuffer, faceMask, failOp, passOp, depthFailOp, compareOp); +} + +// ================================================== +// Acceleration Structure +// ================================================== + +PalResult PAL_CALL palCreateAccelerationstructure( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs) +{ + if (!device || !info || !outAs) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalAccelerationStructure* as = nullptr; + result = device->backend.vtbl1->createAccelerationstructure(device, info, &as); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + as->backend = device->backend; + *outAs = as; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyAccelerationStructure(PalAccelerationStructure* as) +{ + as->backend.vtbl1->destroyAccelerationstructure(as); +} + +void PAL_CALL palGetAccelerationStructureBuildSize( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size) +{ + device->backend.vtbl1->getAccelerationStructureBuildSize(device, info, size); +} + +// ================================================== +// Buffer +// ================================================== + +PalResult PAL_CALL palCreateBuffer( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer) +{ + if (!device || !info || !outBuffer) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalBuffer* buffer = nullptr; + result = device->backend.vtbl1->createBuffer(device, info, &buffer); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + buffer->backend = device->backend; + *outBuffer = buffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyBuffer(PalBuffer* buffer) +{ + buffer->backend.vtbl1->destroyBuffer(buffer); +} + +void PAL_CALL palGetBufferMemoryRequirements( + PalBuffer* buffer, + PalMemoryRequirements* requirements) +{ + buffer->backend.vtbl1->getBufferMemoryRequirements(buffer, requirements); +} + +void PAL_CALL palComputeInstanceStagingSize( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize) +{ + device->backend.vtbl1->computeInstanceStagingSize(device, instanceCount, outSize); +} + +void PAL_CALL palComputeImageStagingRequirements( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements) +{ + device->backend.vtbl1 + ->computeImageStagingRequirements(device, imageFormat, copyInfo, requirements); +} + +void PAL_CALL palWriteInstanceStaging( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr) +{ + device->backend.vtbl1->writeInstanceStaging(device, instanceCount, instances, ptr); +} + +void PAL_CALL palWriteImageStaging( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr) +{ + device->backend.vtbl1->writeImageStaging(device, imageFormat, copyInfo, srcData, ptr); +} + +PalResult PAL_CALL palBindBufferMemory( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset) +{ + return buffer->backend.vtbl1->bindBufferMemory(buffer, memory, offset); +} + +PalResult PAL_CALL palMapBuffer( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr) +{ + return buffer->backend.vtbl1->mapBuffer(buffer, offset, size, outPtr); +} + +void PAL_CALL palUnmapBuffer(PalBuffer* buffer) +{ + buffer->backend.vtbl1->unmapBuffer(buffer); +} + +PalDeviceAddress PAL_CALL palGetBufferDeviceAddress(PalBuffer* buffer) +{ + return buffer->backend.vtbl1->getBufferDeviceAddress(buffer); +} + +// ================================================== +// Descriptor Pool, Set and Layout +// ================================================== + +PalResult PAL_CALL palCreateDescriptorSetLayout( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout) +{ + if (!device || !info || !outLayout) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalDescriptorSetLayout* layout = nullptr; + result = device->backend.vtbl1->createDescriptorSetLayout(device, info, &layout); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + layout->backend = device->backend; + *outLayout = layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyDescriptorSetLayout(PalDescriptorSetLayout* layout) +{ + layout->backend.vtbl1->destroyDescriptorSetLayout(layout); +} + +PalResult PAL_CALL palCreateDescriptorPool( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool) +{ + if (!device || !info || !outPool) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalDescriptorPool* pool = nullptr; + result = device->backend.vtbl1->createDescriptorPool(device, info, &pool); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + pool->backend = device->backend; + *outPool = pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyDescriptorPool(PalDescriptorPool* pool) +{ + pool->backend.vtbl1->destroyDescriptorPool(pool); +} + +PalResult PAL_CALL palResetDescriptorPool(PalDescriptorPool* pool) +{ + return pool->backend.vtbl1->resetDescriptorPool(pool); +} + +PalResult PAL_CALL palAllocateDescriptorSet( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet) +{ + if (!device || !pool || !layout || !outSet) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalDescriptorSet* set = nullptr; + result = device->backend.vtbl1->allocateDescriptorSet(device, pool, layout, &set); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + set->backend = device->backend; + *outSet = set; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palUpdateDescriptorSet( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos) +{ + return device->backend.vtbl1->updateDescriptorSet(device, count, infos); +} + +// ================================================== +// Pipeline Layout +// ================================================== + +PalResult PAL_CALL palCreatePipelineLayout( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout) +{ + if (!device || !info || !outLayout) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalPipelineLayout* layout = nullptr; + result = device->backend.vtbl1->createPipelineLayout(device, info, &layout); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + layout->backend = device->backend; + *outLayout = layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyPipelineLayout(PalPipelineLayout* layout) +{ + layout->backend.vtbl1->destroyPipelineLayout(layout); +} + +// ================================================== +// Pipeline +// ================================================== + +PalResult PAL_CALL palCreateGraphicsPipeline( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + if (!device || !info || !outPipeline) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalPipeline* pipeline = nullptr; + result = device->backend.vtbl1->createGraphicsPipeline(device, info, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + pipeline->backend = device->backend; + *outPipeline = pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palCreateComputePipeline( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + if (!device || !info || !outPipeline) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalPipeline* pipeline = nullptr; + result = device->backend.vtbl1->createComputePipeline(device, info, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + pipeline->backend = device->backend; + *outPipeline = pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palCreateRayTracingPipeline( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + if (!device || !info || !outPipeline) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalPipeline* pipeline = nullptr; + result = device->backend.vtbl1->createRayTracingPipeline(device, info, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + pipeline->backend = device->backend; + *outPipeline = pipeline; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyPipeline(PalPipeline* pipeline) +{ + pipeline->backend.vtbl1->destroyPipeline(pipeline); +} + +// ================================================== +// Shader Binding Table +// ================================================== + +PalResult PAL_CALL palCreateShaderBindingTable( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt) +{ + if (!device || !info || !outSbt) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + PalResult result; + PalShaderBindingTable* sbt = nullptr; + result = device->backend.vtbl1->createShaderBindingTable(device, info, &sbt); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + + sbt->backend = device->backend; + *outSbt = sbt; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyShaderBindingTable(PalShaderBindingTable* sbt) +{ + sbt->backend.vtbl1->destroyShaderBindingTable(sbt); +} + +void PAL_CALL palUpdateShaderBindingTable( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos) +{ + sbt->backend.vtbl1->updateShaderBindingTable(sbt, count, infos); +} + +// ================================================== +// Utils +// ================================================== + +void PAL_CALL palBuildWorkGroupInfo( + const PalWorkGroupBuildData* data, + uint32_t* count, + PalWorkGroupInfo* infos) +{ + uint32_t workGroupCount[3]; + uint32_t groupInfoCount[3]; + for (int i = 0; i < 3; i++) { + uint32_t tmp = ceil(data->workCount[i], data->workGroupSize[i]); + workGroupCount[i] = tmp; + groupInfoCount[i] = ceil(tmp, data->workGroupCount[i]); + } + + if (!infos) { + // total number of group build info on all axis + *count = groupInfoCount[0] * groupInfoCount[1] * groupInfoCount[2]; + return; + } + + for (int i = 0; i < *count; i++) { + PalWorkGroupInfo* buildInfo = &infos[i]; + // find index + uint32_t index[3]; + index[0] = i % groupInfoCount[0]; + index[1] = (i / groupInfoCount[0]) % groupInfoCount[1]; + index[2] = i / (groupInfoCount[0] * groupInfoCount[1]); + + // fill group build info + for (int j = 0; j < 3; j++) { + buildInfo->workGroupBase[j] = index[j] * data->workGroupCount[j]; + buildInfo->workGroupBase[j] = index[j] * data->workGroupCount[j]; + buildInfo->workGroupBase[j] = index[j] * data->workGroupCount[j]; + + uint32_t tmp = workGroupCount[j] - buildInfo->workGroupBase[j]; + buildInfo->workGroupCount[j] = min(data->workGroupCount[j], tmp); + } + } +} diff --git a/src/graphics/pal_graphics_backends.h b/src/graphics/pal_graphics_backends.h new file mode 100644 index 00000000..59fb0542 --- /dev/null +++ b/src/graphics/pal_graphics_backends.h @@ -0,0 +1,1440 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_GRAPHICS_BACKENDS_H +#define _PAL_GRAPHICS_BACKENDS_H + +#include "pal2/pal_graphics.h" + +// clang-format off +typedef struct { + const PalGraphicsBackendVtable1* vtbl1; +} PalGraphicsVtable; + +// ================================================== +// Vulkan +// ================================================== + +#if PAL_HAS_VULKAN_BACKEND +PalResult PAL_CALL initGraphicsVk( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator); + +void PAL_CALL shutdownGraphicsVk(); + +PalResult PAL_CALL enumerateAdaptersVk( + uint32_t* count, + PalAdapter** outAdapters); + +void PAL_CALL getAdapterInfoVk( + PalAdapter* adapter, + PalAdapterInfo* info); + +void PAL_CALL getAdapterCapabilitiesVk( + PalAdapter* adapter, + PalAdapterCapabilities* caps); + +PalAdapterFeatures PAL_CALL getAdapterFeaturesVk(PalAdapter* adapter); + +uint32_t PAL_CALL getHighestSupportedShaderTargetVk( + PalAdapter* adapter, + PalShaderFormats shaderFormat); + +PalResult PAL_CALL createDeviceVk( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice); + +void PAL_CALL destroyDeviceVk(PalDevice* device); + +uint32_t PAL_CALL getDeviceLostReasonVk(PalDevice* device); + +PalResult PAL_CALL allocateMemoryVk( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory); + +void PAL_CALL freeMemoryVk(PalMemory* memory); + +void PAL_CALL querySamplerAnisotropyCapabilitiesVk( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps); + +void PAL_CALL queryMultiViewCapabilitiesVk( + PalDevice* device, + PalMultiViewCapabilities* caps); + +void PAL_CALL queryMultiViewportCapabilitiesVk( + PalDevice* device, + PalMultiViewportCapabilities* caps); + +void PAL_CALL queryDepthStencilCapabilitiesVk( + PalDevice* device, + PalDepthStencilCapabilities* caps); + +void PAL_CALL queryFragmentShadingRateCapabilitiesVk( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps); + +void PAL_CALL queryMeshShaderCapabilitiesVk( + PalDevice* device, + PalMeshShaderCapabilities* caps); + +void PAL_CALL queryRayTracingCapabilitiesVk( + PalDevice* device, + PalRayTracingCapabilities* caps); + +void PAL_CALL queryDescriptorIndexingCapabilitiesVk( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps); + +PalResult PAL_CALL createQueueVk( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue); + +void PAL_CALL destroyQueueVk(PalQueue* queue); + +PalBool PAL_CALL canQueuePresentVk( + PalQueue* queue, + PalSurface* surface); + +PalResult PAL_CALL waitQueueVk(PalQueue* queue); + +void PAL_CALL enumerateFormatsVk( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats); + +PalBool PAL_CALL isFormatSupportedVk( + PalAdapter* adapter, + PalFormat format); + +PalImageUsages PAL_CALL queryFormatImageUsagesVk( + PalAdapter* adapter, + PalFormat format); + +PalSampleCount PAL_CALL queryFormatSampleCountVk( + PalAdapter* adapter, + PalFormat format); + +PalResult PAL_CALL createImageVk( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage); + +void PAL_CALL destroyImageVk(PalImage* image); + +void PAL_CALL getImageInfoVk( + PalImage* image, + PalImageInfo* info); + +void PAL_CALL getImageMemoryRequirementsVk( + PalImage* image, + PalMemoryRequirements* requirements); + +PalResult PAL_CALL bindImageMemoryVk( + PalImage* image, + PalMemory* memory, + uint64_t offset); + +PalResult PAL_CALL createImageViewVk( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView); + +void PAL_CALL destroyImageViewVk(PalImageView* imageView); + +PalResult PAL_CALL createSamplerVk( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler); + +void PAL_CALL destroySamplerVk(PalSampler* sampler); + +PalResult PAL_CALL createSurfaceVk( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface); + +void PAL_CALL destroySurfaceVk(PalSurface* surface); + +void PAL_CALL getSurfaceCapabilitiesVk( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps); + +PalResult PAL_CALL createSwapchainVk( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain); + +void PAL_CALL destroySwapchainVk(PalSwapchain* swapchain); + +PalImage* PAL_CALL getSwapchainImageVk( + PalSwapchain* swapchain, + uint32_t index); + +PalResult PAL_CALL getNextSwapchainImageVk( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex); + +PalResult PAL_CALL presentSwapchainVk( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore); + +PalResult PAL_CALL resizeSwapchainVk( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight); + +PalResult PAL_CALL createShaderVk( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader); + +void PAL_CALL destroyShaderVk(PalShader* shader); + +PalResult PAL_CALL createFenceVk( + PalDevice* device, + PalBool signaled, + PalFence** outFence); + +void PAL_CALL destroyFenceVk(PalFence* fence); + +PalResult PAL_CALL waitFenceVk( + PalFence* fence, + uint64_t timeout); + +PalResult PAL_CALL resetFenceVk(PalFence* fence); + +PalBool PAL_CALL isFenceSignaledVk(PalFence* fence); + +PalResult PAL_CALL createSemaphoreVk( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore); + +void PAL_CALL destroySemaphoreVk(PalSemaphore* semaphore); + +PalResult PAL_CALL waitSemaphoreVk( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout); + +PalResult PAL_CALL signalSemaphoreVk( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value); + +PalResult PAL_CALL getSemaphoreValueVk( + PalSemaphore* semaphore, + uint64_t* value); + +PalResult PAL_CALL createCommandPoolVk( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool); + +void PAL_CALL destroyCommandPoolVk(PalCommandPool* pool); + +PalResult PAL_CALL allocateCommandBufferVk( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer); + +void PAL_CALL freeCommandBufferVk(PalCommandBuffer* cmdBuffer); + +PalResult PAL_CALL resetCommandBufferVk(PalCommandBuffer* cmdBuffer); + +PalResult PAL_CALL submitCommandBufferVk( + PalQueue* queue, + PalCommandBufferSubmitInfo* info); + +PalResult PAL_CALL cmdBeginVk( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info); + +PalResult PAL_CALL cmdEndVk(PalCommandBuffer* cmdBuffer); + +void PAL_CALL cmdExecuteCommandBufferVk( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer); + +void PAL_CALL cmdSetFragmentShadingRateVk( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state); + +void PAL_CALL cmdDrawMeshTasksVk( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +void PAL_CALL cmdDrawMeshTasksIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount); + +void PAL_CALL cmdDrawMeshTasksIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + +void PAL_CALL cmdBuildAccelerationStructureVk( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info); + +void PAL_CALL cmdBeginRenderingVk( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info); + +void PAL_CALL cmdEndRenderingVk(PalCommandBuffer* cmdBuffer); + +void PAL_CALL cmdCopyBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo); + +void PAL_CALL cmdCopyBufferToImageVk( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo); + +void PAL_CALL cmdCopyImageVk( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo); + +void PAL_CALL cmdCopyImageToBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo); + +void PAL_CALL cmdBindPipelineVk( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline); + +void PAL_CALL cmdSetViewportVk( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports); + +void PAL_CALL cmdSetScissorsVk( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors); + +void PAL_CALL cmdBindVertexBuffersVk( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets); + +void PAL_CALL cmdBindIndexBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type); + +void PAL_CALL cmdDrawVk( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + +void PAL_CALL cmdDrawIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +void PAL_CALL cmdDrawIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + +void PAL_CALL cmdDrawIndexedVk( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + +void PAL_CALL cmdDrawIndexedIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +void PAL_CALL cmdDrawIndexedIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + +void PAL_CALL cmdAccelerationStructureBarrierVk( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info); + +void PAL_CALL cmdImageBarrierVk( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info); + +void PAL_CALL cmdBufferBarrierVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info); + +void PAL_CALL cmdDispatchVk( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +void PAL_CALL cmdDispatchBaseVk( + PalCommandBuffer* cmdBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +void PAL_CALL cmdDispatchIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer); + +void PAL_CALL cmdTraceRaysVk( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth); + +void PAL_CALL cmdTraceRaysIndirectVk( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer); + +void PAL_CALL cmdBindDescriptorSetVk( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set); + +void PAL_CALL cmdPushConstantsVk( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value); + +void PAL_CALL cmdSetCullModeVk( + PalCommandBuffer* cmdBuffer, + PalCullMode cullMode); + +void PAL_CALL cmdSetFrontFaceVk( + PalCommandBuffer* cmdBuffer, + PalFrontFace frontFace); + +void PAL_CALL cmdSetPrimitiveTopologyVk( + PalCommandBuffer* cmdBuffer, + PalPrimitiveTopology topology); + +void PAL_CALL cmdSetDepthTestEnableVk( + PalCommandBuffer* cmdBuffer, + PalBool enable); + +void PAL_CALL cmdSetDepthWriteEnableVk( + PalCommandBuffer* cmdBuffer, + PalBool enable); + +void PAL_CALL cmdSetStencilOpVk( + PalCommandBuffer* cmdBuffer, + PalStencilFaceFlags faceMask, + PalStencilOp failOp, + PalStencilOp passOp, + PalStencilOp depthFailOp, + PalCompareOp compareOp); + +PalResult PAL_CALL createAccelerationstructureVk( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs); + +void PAL_CALL destroyAccelerationstructureVk(PalAccelerationStructure* as); + +void PAL_CALL getAccelerationStructureBuildSizeVk( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size); + +PalResult PAL_CALL createBufferVk( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer); + +void PAL_CALL destroyBufferVk(PalBuffer* buffer); + +void PAL_CALL getBufferMemoryRequirementsVk( + PalBuffer* buffer, + PalMemoryRequirements* requirements); + +void PAL_CALL computeInstanceStagingSizeVk( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize); + +void PAL_CALL computeImageStagingRequirementsVk( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements); + +void PAL_CALL writeInstanceStagingVk( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr); + +void PAL_CALL writeImageStagingVk( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr); + +PalResult PAL_CALL bindBufferMemoryVk( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset); + +PalResult PAL_CALL mapBufferVk( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr); + +void PAL_CALL unmapBufferVk(PalBuffer* buffer); + +PalDeviceAddress PAL_CALL getBufferDeviceAddressVk(PalBuffer* buffer); + +PalResult PAL_CALL createDescriptorSetLayoutVk( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout); + +void PAL_CALL destroyDescriptorSetLayoutVk(PalDescriptorSetLayout* layout); + +PalResult PAL_CALL createDescriptorPoolVk( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool); + +void PAL_CALL destroyDescriptorPoolVk(PalDescriptorPool* pool); + +PalResult PAL_CALL resetDescriptorPoolVk(PalDescriptorPool* pool); + +PalResult PAL_CALL allocateDescriptorSetVk( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet); + +PalResult PAL_CALL updateDescriptorSetVk( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos); + +PalResult PAL_CALL createPipelineLayoutVk( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout); + +void PAL_CALL destroyPipelineLayoutVk(PalPipelineLayout* layout); + +PalResult PAL_CALL createGraphicsPipelineVk( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline); + +PalResult PAL_CALL createComputePipelineVk( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline); + +PalResult PAL_CALL createRayTracingPipelineVk( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline); + +void PAL_CALL destroyPipelineVk(PalPipeline* pipeline); + +PalResult PAL_CALL createShaderBindingTableVk( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt); + +void PAL_CALL destroyShaderBindingTableVk(PalShaderBindingTable* sbt); + +void PAL_CALL updateShaderBindingTableVk( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos); + +static PalGraphicsBackendVtable1 s_VkBackend1 = { + .enumerateAdapters = enumerateAdaptersVk, + .getAdapterInfo = getAdapterInfoVk, + .getAdapterCapabilities = getAdapterCapabilitiesVk, + .getAdapterFeatures = getAdapterFeaturesVk, + .getHighestSupportedShaderTarget = getHighestSupportedShaderTargetVk, + .createDevice = createDeviceVk, + .destroyDevice = destroyDeviceVk, + .getDeviceLostReason = getDeviceLostReasonVk, + .allocateMemory = allocateMemoryVk, + .freeMemory = freeMemoryVk, + .querySamplerAnisotropyCapabilities = querySamplerAnisotropyCapabilitiesVk, + .queryMultiViewCapabilities = queryMultiViewCapabilitiesVk, + .queryMultiViewportCapabilities = queryMultiViewportCapabilitiesVk, + .queryDepthStencilCapabilities = queryDepthStencilCapabilitiesVk, + .queryFragmentShadingRateCapabilities = queryFragmentShadingRateCapabilitiesVk, + .queryMeshShaderCapabilities = queryMeshShaderCapabilitiesVk, + .queryRayTracingCapabilities = queryRayTracingCapabilitiesVk, + .queryDescriptorIndexingCapabilities = queryDescriptorIndexingCapabilitiesVk, + .createQueue = createQueueVk, + .destroyQueue = destroyQueueVk, + .waitQueue = waitQueueVk, + .canQueuePresent = canQueuePresentVk, + .enumerateFormats = enumerateFormatsVk, + .isFormatSupported = isFormatSupportedVk, + .queryFormatImageUsages = queryFormatImageUsagesVk, + .queryFormatSampleCount = queryFormatSampleCountVk, + .createImage = createImageVk, + .destroyImage = destroyImageVk, + .getImageInfo = getImageInfoVk, + .getImageMemoryRequirements = getImageMemoryRequirementsVk, + .bindImageMemory = bindImageMemoryVk, + .createImageView = createImageViewVk, + .destroyImageView = destroyImageViewVk, + .createSampler = createSamplerVk, + .destroySampler = destroySamplerVk, + .createSurface = createSurfaceVk, + .destroySurface = destroySurfaceVk, + .getSurfaceCapabilities = getSurfaceCapabilitiesVk, + .createSwapchain = createSwapchainVk, + .destroySwapchain = destroySwapchainVk, + .getSwapchainImage = getSwapchainImageVk, + .getNextSwapchainImage = getNextSwapchainImageVk, + .presentSwapchain = presentSwapchainVk, + .resizeSwapchain = resizeSwapchainVk, + .createShader = createShaderVk, + .destroyShader = destroyShaderVk, + .createFence = createFenceVk, + .destroyFence = destroyFenceVk, + .waitFence = waitFenceVk, + .resetFence = resetFenceVk, + .isFenceSignaled = isFenceSignaledVk, + .createSemaphore = createSemaphoreVk, + .destroySemaphore = destroySemaphoreVk, + .waitSemaphore = waitSemaphoreVk, + .signalSemaphore = signalSemaphoreVk, + .getSemaphoreValue = getSemaphoreValueVk, + .createCommandPool = createCommandPoolVk, + .destroyCommandPool = destroyCommandPoolVk, + .allocateCommandBuffer = allocateCommandBufferVk, + .freeCommandBuffer = freeCommandBufferVk, + .resetCommandBuffer = resetCommandBufferVk, + .submitCommandBuffer = submitCommandBufferVk, + .cmdBegin = cmdBeginVk, + .cmdEnd = cmdEndVk, + .cmdExecuteCommandBuffer = cmdExecuteCommandBufferVk, + .cmdSetFragmentShadingRate = cmdSetFragmentShadingRateVk, + .cmdDrawMeshTasks = cmdDrawMeshTasksVk, + .cmdDrawMeshTasksIndirect = cmdDrawMeshTasksIndirectVk, + .cmdDrawMeshTasksIndirectCount = cmdDrawMeshTasksIndirectCountVk, + .cmdBuildAccelerationStructure = cmdBuildAccelerationStructureVk, + .cmdBeginRendering = cmdBeginRenderingVk, + .cmdEndRendering = cmdEndRenderingVk, + .cmdCopyBuffer = cmdCopyBufferVk, + .cmdCopyBufferToImage = cmdCopyBufferToImageVk, + .cmdCopyImage = cmdCopyImageVk, + .cmdCopyImageToBuffer = cmdCopyImageToBufferVk, + .cmdBindPipeline = cmdBindPipelineVk, + .cmdSetViewport = cmdSetViewportVk, + .cmdSetScissors = cmdSetScissorsVk, + .cmdBindVertexBuffers = cmdBindVertexBuffersVk, + .cmdBindIndexBuffer = cmdBindIndexBufferVk, + .cmdDraw = cmdDrawVk, + .cmdDrawIndirect = cmdDrawIndirectVk, + .cmdDrawIndirectCount = cmdDrawIndirectCountVk, + .cmdDrawIndexed = cmdDrawIndexedVk, + .cmdDrawIndexedIndirect = cmdDrawIndexedIndirectVk, + .cmdDrawIndexedIndirectCount = cmdDrawIndexedIndirectCountVk, + .cmdAccelerationStructureBarrier = cmdAccelerationStructureBarrierVk, + .cmdImageBarrier = cmdImageBarrierVk, + .cmdBufferBarrier = cmdBufferBarrierVk, + .cmdDispatch = cmdDispatchVk, + .cmdDispatchBase = cmdDispatchBaseVk, + .cmdDispatchIndirect = cmdDispatchIndirectVk, + .cmdTraceRays = cmdTraceRaysVk, + .cmdTraceRaysIndirect = cmdTraceRaysIndirectVk, + .cmdBindDescriptorSet = cmdBindDescriptorSetVk, + .cmdPushConstants = cmdPushConstantsVk, + .cmdSetCullMode = cmdSetCullModeVk, + .cmdSetFrontFace = cmdSetFrontFaceVk, + .cmdSetPrimitiveTopology = cmdSetPrimitiveTopologyVk, + .cmdSetDepthTestEnable = cmdSetDepthTestEnableVk, + .cmdSetDepthWriteEnable = cmdSetDepthWriteEnableVk, + .cmdSetStencilOp = cmdSetStencilOpVk, + .createAccelerationstructure = createAccelerationstructureVk, + .destroyAccelerationstructure = destroyAccelerationstructureVk, + .getAccelerationStructureBuildSize = getAccelerationStructureBuildSizeVk, + .createBuffer = createBufferVk, + .destroyBuffer = destroyBufferVk, + .getBufferMemoryRequirements = getBufferMemoryRequirementsVk, + .computeInstanceStagingSize = computeInstanceStagingSizeVk, + .computeImageStagingRequirements = computeImageStagingRequirementsVk, + .writeInstanceStaging = writeInstanceStagingVk, + .writeImageStaging = writeImageStagingVk, + .bindBufferMemory = bindBufferMemoryVk, + .getBufferDeviceAddress = getBufferDeviceAddressVk, + .mapBuffer = mapBufferVk, + .unmapBuffer = unmapBufferVk, + .createDescriptorSetLayout = createDescriptorSetLayoutVk, + .destroyDescriptorSetLayout = destroyDescriptorSetLayoutVk, + .createDescriptorPool = createDescriptorPoolVk, + .destroyDescriptorPool = destroyDescriptorPoolVk, + .resetDescriptorPool = resetDescriptorPoolVk, + .allocateDescriptorSet = allocateDescriptorSetVk, + .updateDescriptorSet = updateDescriptorSetVk, + .createPipelineLayout = createPipelineLayoutVk, + .destroyPipelineLayout = destroyPipelineLayoutVk, + .createGraphicsPipeline = createGraphicsPipelineVk, + .createComputePipeline = createComputePipelineVk, + .createRayTracingPipeline = createRayTracingPipelineVk, + .destroyPipeline = destroyPipelineVk, + .createShaderBindingTable = createShaderBindingTableVk, + .destroyShaderBindingTable = destroyShaderBindingTableVk, + .updateShaderBindingTable = updateShaderBindingTableVk}; + +#endif // PAL_HAS_VULKAN_BACKEND + +// ================================================== +// D3D12 +// ================================================== + +#if PAL_HAS_D3D12_BACKEND +PalResult PAL_CALL initGraphicsD3D12( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator); + +void PAL_CALL shutdownGraphicsD3D12(); + +PalResult PAL_CALL enumerateAdaptersD3D12( + uint32_t* count, + PalAdapter** outAdapters); + +void PAL_CALL getAdapterInfoD3D12( + PalAdapter* adapter, + PalAdapterInfo* info); + +void PAL_CALL getAdapterCapabilitiesD3D12( + PalAdapter* adapter, + PalAdapterCapabilities* caps); + +PalAdapterFeatures PAL_CALL getAdapterFeaturesD3D12(PalAdapter* adapter); + +uint32_t PAL_CALL getHighestSupportedShaderTargetD3D12( + PalAdapter* adapter, + PalShaderFormats shaderFormat); + +PalResult PAL_CALL createDeviceD3D12( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice); + +void PAL_CALL destroyDeviceD3D12(PalDevice* device); + +uint32_t PAL_CALL getDeviceLostReasonD3D12(PalDevice* device); + +PalResult PAL_CALL allocateMemoryD3D12( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory); + +void PAL_CALL freeMemoryD3D12(PalMemory* memory); + +void PAL_CALL querySamplerAnisotropyCapabilitiesD3D12( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps); + +void PAL_CALL queryMultiViewCapabilitiesD3D12( + PalDevice* device, + PalMultiViewCapabilities* caps); + +void PAL_CALL queryMultiViewportCapabilitiesD3D12( + PalDevice* device, + PalMultiViewportCapabilities* caps); + +void PAL_CALL queryDepthStencilCapabilitiesD3D12( + PalDevice* device, + PalDepthStencilCapabilities* caps); + +void PAL_CALL queryFragmentShadingRateCapabilitiesD3D12( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps); + +void PAL_CALL queryMeshShaderCapabilitiesD3D12( + PalDevice* device, + PalMeshShaderCapabilities* caps); + +void PAL_CALL queryRayTracingCapabilitiesD3D12( + PalDevice* device, + PalRayTracingCapabilities* caps); + +void PAL_CALL queryDescriptorIndexingCapabilitiesD3D12( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps); + +PalResult PAL_CALL createQueueD3D12( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue); + +void PAL_CALL destroyQueueD3D12(PalQueue* queue); + +PalBool PAL_CALL canQueuePresentD3D12( + PalQueue* queue, + PalSurface* surface); + +PalResult PAL_CALL waitQueueD3D12(PalQueue* queue); + +void PAL_CALL enumerateFormatsD3D12( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats); + +PalBool PAL_CALL isFormatSupportedD3D12( + PalAdapter* adapter, + PalFormat format); + +PalImageUsages PAL_CALL queryFormatImageUsagesD3D12( + PalAdapter* adapter, + PalFormat format); + +PalSampleCount PAL_CALL queryFormatSampleCountD3D12( + PalAdapter* adapter, + PalFormat format); + +PalResult PAL_CALL createImageD3D12( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage); + +void PAL_CALL destroyImageD3D12(PalImage* image); + +void PAL_CALL getImageInfoD3D12( + PalImage* image, + PalImageInfo* info); + +void PAL_CALL getImageMemoryRequirementsD3D12( + PalImage* image, + PalMemoryRequirements* requirements); + +PalResult PAL_CALL bindImageMemoryD3D12( + PalImage* image, + PalMemory* memory, + uint64_t offset); + +PalResult PAL_CALL createImageViewD3D12( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView); + +void PAL_CALL destroyImageViewD3D12(PalImageView* imageView); + +PalResult PAL_CALL createSamplerD3D12( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler); + +void PAL_CALL destroySamplerD3D12(PalSampler* sampler); + +PalResult PAL_CALL createSurfaceD3D12( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface); + +void PAL_CALL destroySurfaceD3D12(PalSurface* surface); + +void PAL_CALL getSurfaceCapabilitiesD3D12( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps); + +PalResult PAL_CALL createSwapchainD3D12( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain); + +void PAL_CALL destroySwapchainD3D12(PalSwapchain* swapchain); + +PalImage* PAL_CALL getSwapchainImageD3D12( + PalSwapchain* swapchain, + uint32_t index); + +PalResult PAL_CALL getNextSwapchainImageD3D12( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex); + +PalResult PAL_CALL presentSwapchainD3D12( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore); + +PalResult PAL_CALL resizeSwapchainD3D12( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight); + +PalResult PAL_CALL createShaderD3D12( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader); + +void PAL_CALL destroyShaderD3D12(PalShader* shader); + +PalResult PAL_CALL createFenceD3D12( + PalDevice* device, + PalBool signaled, + PalFence** outFence); + +void PAL_CALL destroyFenceD3D12(PalFence* fence); + +PalResult PAL_CALL waitFenceD3D12( + PalFence* fence, + uint64_t timeout); + +PalResult PAL_CALL resetFenceD3D12(PalFence* fence); + +PalBool PAL_CALL isFenceSignaledD3D12(PalFence* fence); + +PalResult PAL_CALL createSemaphoreD3D12( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore); + +void PAL_CALL destroySemaphoreD3D12(PalSemaphore* semaphore); + +PalResult PAL_CALL waitSemaphoreD3D12( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout); + +PalResult PAL_CALL signalSemaphoreD3D12( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value); + +PalResult PAL_CALL getSemaphoreValueD3D12( + PalSemaphore* semaphore, + uint64_t* value); + +PalResult PAL_CALL createCommandPoolD3D12( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool); + +void PAL_CALL destroyCommandPoolD3D12(PalCommandPool* pool); + +PalResult PAL_CALL allocateCommandBufferD3D12( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer); + +void PAL_CALL freeCommandBufferD3D12(PalCommandBuffer* cmdBuffer); + +PalResult PAL_CALL resetCommandBufferD3D12(PalCommandBuffer* cmdBuffer); + +PalResult PAL_CALL submitCommandBufferD3D12( + PalQueue* queue, + PalCommandBufferSubmitInfo* info); + +PalResult PAL_CALL cmdBeginD3D12( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info); + +PalResult PAL_CALL cmdEndD3D12(PalCommandBuffer* cmdBuffer); + +void PAL_CALL cmdExecuteCommandBufferD3D12( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer); + +void PAL_CALL cmdSetFragmentShadingRateD3D12( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state); + +void PAL_CALL cmdDrawMeshTasksD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +void PAL_CALL cmdDrawMeshTasksIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount); + +void PAL_CALL cmdDrawMeshTasksIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount); + +void PAL_CALL cmdBuildAccelerationStructureD3D12( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info); + +void PAL_CALL cmdBeginRenderingD3D12( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info); + +void PAL_CALL cmdEndRenderingD3D12(PalCommandBuffer* cmdBuffer); + +void PAL_CALL cmdCopyBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo); + +void PAL_CALL cmdCopyBufferToImageD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo); + +void PAL_CALL cmdCopyImageD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo); + +void PAL_CALL cmdCopyImageToBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo); + +void PAL_CALL cmdBindPipelineD3D12( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline); + +void PAL_CALL cmdSetViewportD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports); + +void PAL_CALL cmdSetScissorsD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors); + +void PAL_CALL cmdBindVertexBuffersD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets); + +void PAL_CALL cmdBindIndexBufferD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type); + +void PAL_CALL cmdDrawD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance); + +void PAL_CALL cmdDrawIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +void PAL_CALL cmdDrawIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + +void PAL_CALL cmdDrawIndexedD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance); + +void PAL_CALL cmdDrawIndexedIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count); + +void PAL_CALL cmdDrawIndexedIndirectCountD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t count); + +void PAL_CALL cmdAccelerationStructureBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info); + +void PAL_CALL cmdImageBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info); + +void PAL_CALL cmdBufferBarrierD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info); + +void PAL_CALL cmdDispatchD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ); + +void PAL_CALL cmdDispatchIndirectD3D12( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer); + +void PAL_CALL cmdTraceRaysD3D12( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth); + +void PAL_CALL cmdTraceRaysIndirectD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer); + +void PAL_CALL cmdBindDescriptorSetD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set); + +void PAL_CALL cmdPushConstantsD3D12( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value); + +PalResult PAL_CALL createAccelerationstructureD3D12( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs); + +void PAL_CALL destroyAccelerationstructureD3D12(PalAccelerationStructure* as); + +void PAL_CALL getAccelerationStructureBuildSizeD3D12( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size); + +PalResult PAL_CALL createBufferD3D12( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer); + +void PAL_CALL destroyBufferD3D12(PalBuffer* buffer); + +void PAL_CALL getBufferMemoryRequirementsD3D12( + PalBuffer* buffer, + PalMemoryRequirements* requirements); + +void PAL_CALL computeInstanceStagingSizeD3D12( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize); + +void PAL_CALL computeImageStagingRequirementsD3D12( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements); + +void PAL_CALL writeInstanceStagingD3D12( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr); + +void PAL_CALL writeImageStagingD3D12( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr); + +PalResult PAL_CALL bindBufferMemoryD3D12( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset); + +PalResult PAL_CALL mapBufferD3D12( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr); + +void PAL_CALL unmapBufferD3D12(PalBuffer* buffer); + +PalDeviceAddress PAL_CALL getBufferDeviceAddressD3D12(PalBuffer* buffer); + +PalResult PAL_CALL createDescriptorSetLayoutD3D12( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout); + +void PAL_CALL destroyDescriptorSetLayoutD3D12(PalDescriptorSetLayout* layout); + +PalResult PAL_CALL createDescriptorPoolD3D12( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool); + +void PAL_CALL destroyDescriptorPoolD3D12(PalDescriptorPool* pool); + +PalResult PAL_CALL resetDescriptorPoolD3D12(PalDescriptorPool* pool); + +PalResult PAL_CALL allocateDescriptorSetD3D12( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet); + +PalResult PAL_CALL updateDescriptorSetD3D12( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos); + +PalResult PAL_CALL createPipelineLayoutD3D12( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout); + +void PAL_CALL destroyPipelineLayoutD3D12(PalPipelineLayout* layout); + +PalResult PAL_CALL createGraphicsPipelineD3D12( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline); + +PalResult PAL_CALL createComputePipelineD3D12( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline); + +PalResult PAL_CALL createRayTracingPipelineD3D12( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline); + +void PAL_CALL destroyPipelineD3D12(PalPipeline* pipeline); + +PalResult PAL_CALL createShaderBindingTableD3D12( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt); + +void PAL_CALL destroyShaderBindingTableD3D12(PalShaderBindingTable* sbt); + +void PAL_CALL updateShaderBindingTableD3D12( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos); + +static PalGraphicsBackendVtable1 s_D3D12Backend1 = { + .enumerateAdapters = enumerateAdaptersD3D12, + .getAdapterInfo = getAdapterInfoD3D12, + .getAdapterCapabilities = getAdapterCapabilitiesD3D12, + .getAdapterFeatures = getAdapterFeaturesD3D12, + .getHighestSupportedShaderTarget = getHighestSupportedShaderTargetD3D12, + .createDevice = createDeviceD3D12, + .destroyDevice = destroyDeviceD3D12, + .getDeviceLostReason = getDeviceLostReasonD3D12, + .allocateMemory = allocateMemoryD3D12, + .freeMemory = freeMemoryD3D12, + .querySamplerAnisotropyCapabilities = querySamplerAnisotropyCapabilitiesD3D12, + .queryMultiViewCapabilities = queryMultiViewCapabilitiesD3D12, + .queryMultiViewportCapabilities = queryMultiViewportCapabilitiesD3D12, + .queryDepthStencilCapabilities = queryDepthStencilCapabilitiesD3D12, + .queryFragmentShadingRateCapabilities = queryFragmentShadingRateCapabilitiesD3D12, + .queryMeshShaderCapabilities = queryMeshShaderCapabilitiesD3D12, + .queryRayTracingCapabilities = queryRayTracingCapabilitiesD3D12, + .queryDescriptorIndexingCapabilities = queryDescriptorIndexingCapabilitiesD3D12, + .createQueue = createQueueD3D12, + .destroyQueue = destroyQueueD3D12, + .waitQueue = waitQueueD3D12, + .canQueuePresent = canQueuePresentD3D12, + .enumerateFormats = enumerateFormatsD3D12, + .isFormatSupported = isFormatSupportedD3D12, + .queryFormatImageUsages = queryFormatImageUsagesD3D12, + .queryFormatSampleCount = queryFormatSampleCountD3D12, + .createImage = createImageD3D12, + .destroyImage = destroyImageD3D12, + .getImageInfo = getImageInfoD3D12, + .getImageMemoryRequirements = getImageMemoryRequirementsD3D12, + .bindImageMemory = bindImageMemoryD3D12, + .createImageView = createImageViewD3D12, + .destroyImageView = destroyImageViewD3D12, + .createSampler = createSamplerD3D12, + .destroySampler = destroySamplerD3D12, + .createSurface = createSurfaceD3D12, + .destroySurface = destroySurfaceD3D12, + .getSurfaceCapabilities = getSurfaceCapabilitiesD3D12, + .createSwapchain = createSwapchainD3D12, + .destroySwapchain = destroySwapchainD3D12, + .getSwapchainImage = getSwapchainImageD3D12, + .getNextSwapchainImage = getNextSwapchainImageD3D12, + .presentSwapchain = presentSwapchainD3D12, + .resizeSwapchain = resizeSwapchainD3D12, + .createShader = createShaderD3D12, + .destroyShader = destroyShaderD3D12, + .createFence = createFenceD3D12, + .destroyFence = destroyFenceD3D12, + .waitFence = waitFenceD3D12, + .resetFence = resetFenceD3D12, + .isFenceSignaled = isFenceSignaledD3D12, + .createSemaphore = createSemaphoreD3D12, + .destroySemaphore = destroySemaphoreD3D12, + .waitSemaphore = waitSemaphoreD3D12, + .signalSemaphore = signalSemaphoreD3D12, + .getSemaphoreValue = getSemaphoreValueD3D12, + .createCommandPool = createCommandPoolD3D12, + .destroyCommandPool = destroyCommandPoolD3D12, + .allocateCommandBuffer = allocateCommandBufferD3D12, + .freeCommandBuffer = freeCommandBufferD3D12, + .resetCommandBuffer = resetCommandBufferD3D12, + .submitCommandBuffer = submitCommandBufferD3D12, + .cmdBegin = cmdBeginD3D12, + .cmdEnd = cmdEndD3D12, + .cmdExecuteCommandBuffer = cmdExecuteCommandBufferD3D12, + .cmdSetFragmentShadingRate = cmdSetFragmentShadingRateD3D12, + .cmdDrawMeshTasks = cmdDrawMeshTasksD3D12, + .cmdDrawMeshTasksIndirect = cmdDrawMeshTasksIndirectD3D12, + .cmdDrawMeshTasksIndirectCount = cmdDrawMeshTasksIndirectCountD3D12, + .cmdBuildAccelerationStructure = cmdBuildAccelerationStructureD3D12, + .cmdBeginRendering = cmdBeginRenderingD3D12, + .cmdEndRendering = cmdEndRenderingD3D12, + .cmdCopyBuffer = cmdCopyBufferD3D12, + .cmdCopyBufferToImage = cmdCopyBufferToImageD3D12, + .cmdCopyImage = cmdCopyImageD3D12, + .cmdCopyImageToBuffer = cmdCopyImageToBufferD3D12, + .cmdBindPipeline = cmdBindPipelineD3D12, + .cmdSetViewport = cmdSetViewportD3D12, + .cmdSetScissors = cmdSetScissorsD3D12, + .cmdBindVertexBuffers = cmdBindVertexBuffersD3D12, + .cmdBindIndexBuffer = cmdBindIndexBufferD3D12, + .cmdDraw = cmdDrawD3D12, + .cmdDrawIndirect = cmdDrawIndirectD3D12, + .cmdDrawIndirectCount = cmdDrawIndirectCountD3D12, + .cmdDrawIndexed = cmdDrawIndexedD3D12, + .cmdDrawIndexedIndirect = cmdDrawIndexedIndirectD3D12, + .cmdDrawIndexedIndirectCount = cmdDrawIndexedIndirectCountD3D12, + .cmdAccelerationStructureBarrier = cmdAccelerationStructureBarrierD3D12, + .cmdImageBarrier = cmdImageBarrierD3D12, + .cmdBufferBarrier = cmdBufferBarrierD3D12, + .cmdDispatch = cmdDispatchD3D12, + .cmdDispatchBase = nullptr, + .cmdDispatchIndirect = cmdDispatchIndirectD3D12, + .cmdTraceRays = cmdTraceRaysD3D12, + .cmdTraceRaysIndirect = cmdTraceRaysIndirectD3D12, + .cmdBindDescriptorSet = cmdBindDescriptorSetD3D12, + .cmdPushConstants = cmdPushConstantsD3D12, + .cmdSetCullMode = nullptr, + .cmdSetFrontFace = nullptr, + .cmdSetPrimitiveTopology = nullptr, + .cmdSetDepthTestEnable = nullptr, + .cmdSetDepthWriteEnable = nullptr, + .cmdSetStencilOp = nullptr, + .createAccelerationstructure = createAccelerationstructureD3D12, + .destroyAccelerationstructure = destroyAccelerationstructureD3D12, + .getAccelerationStructureBuildSize = getAccelerationStructureBuildSizeD3D12, + .createBuffer = createBufferD3D12, + .destroyBuffer = destroyBufferD3D12, + .getBufferMemoryRequirements = getBufferMemoryRequirementsD3D12, + .computeInstanceStagingSize = computeInstanceStagingSizeD3D12, + .computeImageStagingRequirements = computeImageStagingRequirementsD3D12, + .writeInstanceStaging = writeInstanceStagingD3D12, + .writeImageStaging = writeImageStagingD3D12, + .bindBufferMemory = bindBufferMemoryD3D12, + .getBufferDeviceAddress = getBufferDeviceAddressD3D12, + .mapBuffer = mapBufferD3D12, + .unmapBuffer = unmapBufferD3D12, + .createDescriptorSetLayout = createDescriptorSetLayoutD3D12, + .destroyDescriptorSetLayout = destroyDescriptorSetLayoutD3D12, + .createDescriptorPool = createDescriptorPoolD3D12, + .destroyDescriptorPool = destroyDescriptorPoolD3D12, + .resetDescriptorPool = resetDescriptorPoolD3D12, + .allocateDescriptorSet = allocateDescriptorSetD3D12, + .updateDescriptorSet = updateDescriptorSetD3D12, + .createPipelineLayout = createPipelineLayoutD3D12, + .destroyPipelineLayout = destroyPipelineLayoutD3D12, + .createGraphicsPipeline = createGraphicsPipelineD3D12, + .createComputePipeline = createComputePipelineD3D12, + .createRayTracingPipeline = createRayTracingPipelineD3D12, + .destroyPipeline = destroyPipelineD3D12, + .createShaderBindingTable = createShaderBindingTableD3D12, + .destroyShaderBindingTable = destroyShaderBindingTableD3D12, + .updateShaderBindingTable = updateShaderBindingTableD3D12}; + +#endif // PAL_HAS_D3D12_BACKEND + +// clang-format on +#endif // _PAL_GRAPHICS_BACKENDS_H \ No newline at end of file diff --git a/src/graphics/pal_linear_allocator.h b/src/graphics/pal_linear_allocator.h new file mode 100644 index 00000000..61d2a9ed --- /dev/null +++ b/src/graphics/pal_linear_allocator.h @@ -0,0 +1,50 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_LINEAR_ALLOCATOR_H +#define _PAL_LINEAR_ALLOCATOR_H + +#include "pal2/pal_core.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +typedef struct { + uint8_t* memory; + uint64_t size; + uint64_t offset; +} PalLinearAllocator; + +static void* palLinearAlloc( + PalLinearAllocator* allocator, + uint64_t size, + uint64_t alignment) +{ + uint64_t defaultAlign = alignment; + if (alignment == 0) { + defaultAlign = 16; + } + + uint64_t offset = _ALIGN(allocator->offset, defaultAlign); + if (offset + size > allocator->size) { + // allocate a bigger block + void* block = palAllocate(nullptr, allocator->size * 2, 0); + if (!block) { + return nullptr; + } + + memcpy(block, (const void*)allocator->memory, allocator->size); + palFree(nullptr, (void*)allocator->memory); + allocator->memory = (uint8_t*)block; + allocator->size = allocator->size * 2; + } + + void* ptr = allocator->memory + offset; + allocator->offset = offset + size; + return ptr; +} + +#endif // _PAL_LINEAR_ALLOCATOR_H diff --git a/src/graphics/vulkan/pal_adapter_vulkan.c b/src/graphics/vulkan/pal_adapter_vulkan.c new file mode 100644 index 00000000..787118c6 --- /dev/null +++ b/src/graphics/vulkan/pal_adapter_vulkan.c @@ -0,0 +1,817 @@ +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +static PalSampleCount samplesFromVk(VkSampleCountFlags count) +{ + if (count & VK_SAMPLE_COUNT_2_BIT) { + return PAL_SAMPLE_COUNT_2; + + } else if (count & VK_SAMPLE_COUNT_4_BIT) { + return PAL_SAMPLE_COUNT_4; + + } else if (count & VK_SAMPLE_COUNT_8_BIT) { + return PAL_SAMPLE_COUNT_8; + + } else if (count & VK_SAMPLE_COUNT_16_BIT) { + return PAL_SAMPLE_COUNT_16; + + } else if (count & VK_SAMPLE_COUNT_32_BIT) { + return PAL_SAMPLE_COUNT_32; + + } else if (count & VK_SAMPLE_COUNT_64_BIT) { + return PAL_SAMPLE_COUNT_64; + } + + return PAL_SAMPLE_COUNT_1; +} + +static PalImageUsages imageUsageFromVk(VkFormatFeatureFlags flags) +{ + PalImageUsages usages = 0; + if (flags & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) { + usages |= PAL_IMAGE_USAGE_COLOR_ATTACHEMENT; + } + + if (flags & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) { + usages |= PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT; + } + + if (flags & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT) { + usages |= PAL_IMAGE_USAGE_TRANSFER_SRC; + } + + if (flags & VK_FORMAT_FEATURE_TRANSFER_DST_BIT) { + usages |= PAL_IMAGE_USAGE_TRANSFER_DST; + } + + if (flags & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) { + usages |= VK_IMAGE_USAGE_STORAGE_BIT; + } + + if (flags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) { + usages |= PAL_IMAGE_USAGE_SAMPLED; + } + + return usages; +} + +PalResult PAL_CALL enumerateAdaptersVk( + uint32_t* count, + PalAdapter** outAdapters) +{ + uint32_t deviceCount = 0; + int adapterCount = 0; + uint32_t extCount = 0; + VkResult ret; + VkExtensionProperties* exts = nullptr; + VkPhysicalDeviceProperties props = {0}; + + ret = s_Vk.enumeratePhysicalDevices(s_Vk.instance, &deviceCount, nullptr); + if (ret != VK_SUCCESS) { + return makeResultVk(ret); + } + + if (s_Vk.adapters) { + palFree(s_Vk.allocator, s_Vk.adapters); + } + + VkPhysicalDevice* devices = nullptr; + devices = palAllocate(s_Vk.allocator, sizeof(VkPhysicalDevice) * deviceCount, 0); + s_Vk.adapters = palAllocate(s_Vk.allocator, sizeof(AdapterVk) * deviceCount, 0); + if (!devices || !s_Vk.adapters) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + s_Vk.enumeratePhysicalDevices(s_Vk.instance, &deviceCount, devices); + for (int i = 0; i < deviceCount; i++) { + VkPhysicalDevice phyDevice = devices[i]; + s_Vk.getPhysicalDeviceProperties(phyDevice, &props); + if (props.apiVersion < VK_API_VERSION_1_3) { + // check extension + ret = s_Vk.enumerateDeviceExtensionProperties(phyDevice, nullptr, &extCount, nullptr); + exts = palAllocate(s_Vk.allocator, sizeof(VkExtensionProperties) * extCount, 0); + if (!exts) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + PalBool found = PAL_FALSE; + s_Vk.enumerateDeviceExtensionProperties(phyDevice, nullptr, &extCount, exts); + for (int i = 0; i < extCount; i++) { + const char* ext = exts[i].extensionName; + if (strcmp(ext, "VK_KHR_dynamic_rendering") == 0) { + found = PAL_TRUE; + break; + } + } + + palFree(s_Vk.allocator, exts); + if (!found) { + continue; + } + } + + VkPhysicalDeviceDynamicRenderingFeaturesKHR required = {0}; + required.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + + VkPhysicalDeviceFeatures2KHR features = {0}; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; + features.pNext = &required; + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + + if (!required.dynamicRendering) { + continue; + } + + if (outAdapters) { + if (adapterCount < *count) { + AdapterVk* tmp = &s_Vk.adapters[adapterCount]; + tmp->handle = devices[i]; + outAdapters[adapterCount] = (PalAdapter*)tmp; + } + } + adapterCount++; + } + + if (!outAdapters) { + *count = adapterCount; + } + + palFree(s_Vk.allocator, devices); + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL getAdapterInfoVk( + PalAdapter* adapter, + PalAdapterInfo* info) +{ + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + VkPhysicalDeviceProperties props = {0}; + VkPhysicalDeviceMemoryProperties memProps = {0}; + + s_Vk.getPhysicalDeviceMemoryProperties(phyDevice, &memProps); + s_Vk.getPhysicalDeviceProperties(phyDevice, &props); + + info->apiType = PAL_ADAPTER_API_TYPE_VULKAN; + info->shaderFormats = PAL_SHADER_FORMAT_SPIRV; + info->deviceId = props.deviceID; + info->vendorId = props.vendorID; + info->driverVersion = props.driverVersion; + strcpy(info->name, props.deviceName); + strcpy(info->backendName, "PAL"); + + info->vram = 0; + info->sharedMemory = 0; + for (int i = 0; i < memProps.memoryHeapCount; i++) { + if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) { + info->vram += memProps.memoryHeaps[i].size; + } else { + info->sharedMemory += memProps.memoryHeaps[i].size; + } + } + + // get device type + switch (props.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { + info->type = PAL_ADAPTER_TYPE_INTEGRATED; + break; + } + + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { + info->type = PAL_ADAPTER_TYPE_DISCRETE; + break; + } + + case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: { + info->type = PAL_ADAPTER_TYPE_VIRTUAL; + break; + } + + case VK_PHYSICAL_DEVICE_TYPE_CPU: { + info->type = PAL_ADAPTER_TYPE_CPU; + break; + } + + default: { + info->type = PAL_ADAPTER_TYPE_UNKNOWN; + break; + } + } +} + +void PAL_CALL getAdapterCapabilitiesVk( + PalAdapter* adapter, + PalAdapterCapabilities* caps) +{ + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + + VkPhysicalDeviceProperties props = {0}; + s_Vk.getPhysicalDeviceProperties(phyDevice, &props); + VkPhysicalDeviceLimits* limits = &props.limits; + + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + VkPhysicalDeviceAccelerationStructurePropertiesKHR accProps = {0}; + accProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; + properties2.pNext = &accProps; + s_Vk.getPhysicalDeviceProperties2(phyDevice, &properties2); + + PalViewportCapabilities* viewportCaps = &caps->viewportCaps; + PalImageCapabilities* imageCaps = &caps->imageCaps; + PalResourceCapabilities* resourceCaps = &caps->resourceCaps; + PalComputeCapabilities* computeCaps = &caps->computeCaps; + + // get supported queue commands + uint32_t count = 0; + s_Vk.getPhysicalDeviceQueueFamilyProperties(phyDevice, &count, nullptr); + + VkQueueFamilyProperties* queueProps = nullptr; + queueProps = palAllocate(s_Vk.allocator, sizeof(VkQueueFamilyProperties) * count, 0); + s_Vk.getPhysicalDeviceQueueFamilyProperties(phyDevice, &count, queueProps); + + caps->maxComputeQueues = 0; + caps->maxGraphicsQueues = 0; + caps->maxCopyQueues = 0; + + for (int i = 0; i < count; i++) { + if (queueProps[i].queueFlags & VK_QUEUE_COMPUTE_BIT) { + caps->maxComputeQueues += queueProps->queueCount; + } + + if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + caps->maxGraphicsQueues += queueProps->queueCount; + } + + if (queueProps[i].queueFlags & VK_QUEUE_TRANSFER_BIT) { + caps->maxCopyQueues += queueProps->queueCount; + } + } + + caps->maxColorAttachments = limits->maxColorAttachments; + caps->maxUniformBufferSize = limits->maxUniformBufferRange; + caps->maxStorageBufferSize = limits->maxStorageBufferRange; + caps->maxPushConstantSize = limits->maxPushConstantsSize; + + caps->maxVertexLayouts = limits->maxVertexInputBindings; + caps->maxVertexAttributes = limits->maxVertexInputAttributes; + caps->maxTessellationPatchPoint = limits->maxTessellationPatchSize; + + // viewport limits + viewportCaps->maxWidth = limits->maxViewportDimensions[0]; + viewportCaps->maxHeight = limits->maxViewportDimensions[1]; + viewportCaps->minBoundsRange = limits->viewportBoundsRange[0]; + viewportCaps->maxBoundsRange = limits->viewportBoundsRange[1]; + + // image limits + imageCaps->maxWidth = limits->maxImageDimension2D; + imageCaps->maxHeight = limits->maxImageDimension2D; + imageCaps->maxDepth = limits->maxImageDimension3D; + imageCaps->maxArrayLayers = limits->maxImageArrayLayers; + + // vulkan does not give this but we calculate from the max width and width + uint32_t a = imageCaps->maxWidth; + uint32_t b = imageCaps->maxHeight; + uint32_t c = imageCaps->maxDepth; + + uint32_t tmp = a > b ? a : b; + uint32_t size = tmp > c ? tmp : c; + uint32_t levels = 0; + while (size > 0) { + // divide by two + size = size / 2; + levels++; + } + imageCaps->maxMipLevels = levels; + + // resource limits + resourceCaps->maxPerStageSampledImages = limits->maxPerStageDescriptorSampledImages; + resourceCaps->maxPerSetSampledImages = limits->maxDescriptorSetSampledImages; + resourceCaps->maxPerStageStorageImages = limits->maxPerStageDescriptorStorageImages; + resourceCaps->maxPerSetStorageImages = limits->maxDescriptorSetStorageImages; + + resourceCaps->maxPerStageSamplers = limits->maxPerStageDescriptorSamplers; + resourceCaps->maxPerSetSamplers = limits->maxDescriptorSetSamplers; + resourceCaps->maxPerStageStorageBuffers = limits->maxPerStageDescriptorStorageBuffers; + resourceCaps->maxPerSetStorageBuffers = limits->maxDescriptorSetStorageBuffers; + + resourceCaps->maxPerStageUniformBuffers = limits->maxPerStageDescriptorUniformBuffers; + resourceCaps->maxPerSetUniformBuffers = limits->maxDescriptorSetUniformBuffers; + + tmp = accProps.maxPerStageDescriptorAccelerationStructures; + resourceCaps->maxPerStageAccelerationStructure = tmp; + resourceCaps->maxPerSetAccelerationStructure = accProps.maxDescriptorSetAccelerationStructures; + resourceCaps->maxBoundSets = limits->maxBoundDescriptorSets; + + // compute limits + computeCaps->maxWorkGroupInvocations = limits->maxComputeWorkGroupInvocations; + computeCaps->maxWorkGroupCount[0] = limits->maxComputeWorkGroupCount[0]; + computeCaps->maxWorkGroupCount[1] = limits->maxComputeWorkGroupCount[1]; + computeCaps->maxWorkGroupCount[2] = limits->maxComputeWorkGroupCount[2]; + computeCaps->maxWorkGroupSize[0] = limits->maxComputeWorkGroupSize[0]; + computeCaps->maxWorkGroupSize[1] = limits->maxComputeWorkGroupSize[1]; + computeCaps->maxWorkGroupSize[2] = limits->maxComputeWorkGroupSize[2]; + + palFree(s_Vk.allocator, queueProps); +} + +PalAdapterFeatures PAL_CALL getAdapterFeaturesVk(PalAdapter* adapter) +{ + VkResult result; + PalAdapterFeatures adapterFeatures = 0; + uint32_t extensionCount = 0; + VkPhysicalDeviceProperties props = {0}; + + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + + // get supported extensions + s_Vk.getPhysicalDeviceProperties(phyDevice, &props); + result = s_Vk.enumerateDeviceExtensionProperties(phyDevice, nullptr, &extensionCount, nullptr); + if (result != VK_SUCCESS) { + // we just return without any modern features which is rare + return 0; + } + + VkExtensionProperties* extensionProps = nullptr; + extensionProps = palAllocate(s_Vk.allocator, sizeof(VkExtensionProperties) * extensionCount, 0); + if (!extensionProps) { + return 0; + } + + // check extensions + PalBool hasRayTracing = PAL_FALSE; + PalBool hasAccelerationStructure = PAL_FALSE; + PalBool hasMeshShader = PAL_FALSE; + PalBool hasFragmentRateShading = PAL_FALSE; + PalBool hasTimelineSemaphore = PAL_FALSE; + PalBool hasDescriptorIndexing = PAL_FALSE; + PalBool hasShaderFloat16 = PAL_FALSE; + PalBool hasMultiiView = PAL_FALSE; + PalBool hasDynamicstate = PAL_FALSE; + PalBool hasBufferDeviceAddress = PAL_FALSE; + PalBool hasShaderParameters = PAL_FALSE; + PalBool hasNullDescriptors = PAL_FALSE; + PalBool hasRayQuery = PAL_FALSE; + s_Vk.enumerateDeviceExtensionProperties(phyDevice, nullptr, &extensionCount, extensionProps); + + // clang-format off + // check if the extensions are present + for (int i = 0; i < extensionCount; i++) { + VkExtensionProperties* props = &extensionProps[i]; + if (strcmp(props->extensionName, "VK_KHR_ray_tracing_pipeline") == 0) { + hasRayTracing = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_acceleration_structure") == 0) { + hasAccelerationStructure = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_EXT_mesh_shader") == 0) { + hasMeshShader = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_fragment_shading_rate") == 0) { + hasFragmentRateShading = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_EXT_descriptor_indexing") == 0) { + hasDescriptorIndexing = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_swapchain") == 0) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SWAPCHAIN; + + } else if (strcmp(props->extensionName, "VK_KHR_shader_float16_int8") == 0) { + hasShaderFloat16 = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_timeline_semaphore") == 0) { + hasTimelineSemaphore = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_multiview") == 0) { + hasMultiiView = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_EXT_extended_dynamic_state") == 0) { + hasDynamicstate = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_EXT_extended_dynamic_state2") == 0) { + VkPhysicalDeviceExtendedDynamicState2FeaturesEXT dynState2 = {0}; + dynState2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &dynState2; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (dynState2.extendedDynamicState2) { + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE; + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE; + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP; + } + + } else if (strcmp(props->extensionName, "VK_KHR_depth_stencil_resolve") == 0) { + adapterFeatures |= PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE; + + } else if (strcmp(props->extensionName, "VK_KHR_draw_indirect_count") == 0) { + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT; + + } else if (strcmp(props->extensionName, "VK_KHR_draw_mesh_tasks_indirect_count") == 0) { + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT; + + } else if (strcmp(props->extensionName, "VK_KHR_buffer_device_address") == 0) { + hasBufferDeviceAddress = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_KHR_shader_draw_parameters") == 0) { + hasShaderParameters = PAL_TRUE; + + } else if (strcmp(props->extensionName, "VK_EXT_robustness2") == 0) { + hasNullDescriptors = PAL_TRUE; + } + } + + // features that require additional checks + if (hasRayTracing && hasAccelerationStructure) { + VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray = {0}; + VkPhysicalDeviceAccelerationStructureFeaturesKHR acc = {0}; + + ray.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + acc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + + ray.pNext = &acc; + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &ray; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (ray.rayTracingPipeline && acc.accelerationStructure) { + adapterFeatures |= PAL_ADAPTER_FEATURE_RAY_TRACING; + } + + if (ray.rayTracingPipelineTraceRaysIndirect) { + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING; + } + } + + if (hasMeshShader) { + VkPhysicalDeviceMeshShaderFeaturesEXT mesh = {0}; + mesh.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &mesh; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (mesh.meshShader && mesh.taskShader) { + adapterFeatures |= PAL_ADAPTER_FEATURE_MESH_SHADER; + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH; + } + } + + if (hasFragmentRateShading) { + VkPhysicalDeviceFragmentShadingRateFeaturesKHR frag = {0}; + frag.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &frag; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (frag.pipelineFragmentShadingRate) { + adapterFeatures |= PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE; + } + + if (frag.attachmentFragmentShadingRate) { + adapterFeatures |= + PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2 || hasDescriptorIndexing) { + VkPhysicalDeviceDescriptorIndexingFeaturesEXT desc = {0}; + desc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &desc; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (desc.runtimeDescriptorArray) { + adapterFeatures |= PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2 || hasTimelineSemaphore) { + VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline = {0}; + timeline.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &timeline; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (timeline.timelineSemaphore) { + adapterFeatures |= PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2 || hasShaderFloat16) { + VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader16 = {0}; + shader16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &shader16; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (shader16.shaderFloat16) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SHADER_FLOAT16; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_1 || hasMultiiView) { + VkPhysicalDeviceMultiviewFeaturesKHR multiView = {0}; + multiView.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &multiView; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (multiView.multiview) { + adapterFeatures |= PAL_ADAPTER_FEATURE_MULTI_VIEW; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_3 || hasDynamicstate) { + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynState = {0}; + dynState.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &dynState; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (dynState.extendedDynamicState) { + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE; + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE; + adapterFeatures |= PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2 || hasBufferDeviceAddress) { + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR bufferAddress = {0}; + bufferAddress.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &bufferAddress; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (bufferAddress.bufferDeviceAddress) { + adapterFeatures |= PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2) { + VkPhysicalDeviceVulkan12Features features12 = {0}; + features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &features12; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (features12.drawIndirectCount) { + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT; + } + } + + if (props.apiVersion >= VK_API_VERSION_1_2 || hasShaderParameters) { + VkPhysicalDeviceShaderDrawParametersFeatures drawParameters = {0}; + drawParameters.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &drawParameters; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (drawParameters.shaderDrawParameters) { + adapterFeatures |= PAL_ADAPTER_FEATURE_DISPATCH_BASE; + } + } + + if (hasNullDescriptors) { + VkPhysicalDeviceRobustness2FeaturesEXT nullDescriptors = {0}; + nullDescriptors.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &nullDescriptors; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (nullDescriptors.nullDescriptor) { + adapterFeatures |= PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS; + } + } + + if (hasRayQuery) { + VkPhysicalDeviceRayQueryFeaturesKHR query = {0}; + query.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &query; + + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + if (query.rayQuery) { + adapterFeatures |= PAL_ADAPTER_FEATURE_RAY_QUERY; + } + } + // clang-format on + + VkPhysicalDeviceFeatures features; + s_Vk.getPhysicalDeviceFeatures(phyDevice, &features); + + // check for additional features + if (features.multiViewport) { + adapterFeatures |= PAL_ADAPTER_FEATURE_MULTI_VIEWPORT; + } + + if (features.samplerAnisotropy) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY; + } + + if (features.sampleRateShading) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING; + } + + if (features.shaderFloat64) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SHADER_FLOAT64; + } + + if (features.shaderInt64) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SHADER_INT64; + } + + if (features.shaderInt16) { + adapterFeatures |= PAL_ADAPTER_FEATURE_SHADER_INT16; + } + + if (features.geometryShader) { + adapterFeatures |= PAL_ADAPTER_FEATURE_GEOMETRY_SHADER; + } + + if (features.tessellationShader) { + adapterFeatures |= PAL_ADAPTER_FEATURE_TESSELLATION_SHADER; + } + + if (features.fillModeNonSolid) { + adapterFeatures |= PAL_ADAPTER_FEATURE_POLYGON_MODE_LINE; + } + + // this features are supported on vulkan + adapterFeatures |= PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY; + adapterFeatures |= PAL_ADAPTER_FEATURE_FENCE_RESET; + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW; + adapterFeatures |= PAL_ADAPTER_FEATURE_INDIRECT_DISPATCH; + + palFree(s_Vk.allocator, extensionProps); + return adapterFeatures; +} + +uint32_t PAL_CALL getHighestSupportedShaderTargetVk( + PalAdapter* adapter, + PalShaderFormats shaderFormat) +{ + if (shaderFormat != PAL_SHADER_FORMAT_SPIRV) { + return 0; + } + + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDeviceProperties props = {0}; + s_Vk.getPhysicalDeviceProperties(adapterImpl->handle, &props); + + if (props.apiVersion >= VK_API_VERSION_1_3) { + return PAL_MAKE_SHADER_TARGET(1, 6); + + } else if (props.apiVersion >= VK_API_VERSION_1_2) { + return PAL_MAKE_SHADER_TARGET(1, 5); + + } else if (props.apiVersion >= VK_API_VERSION_1_1) { + return PAL_MAKE_SHADER_TARGET(1, 4); + + } else if (props.apiVersion >= VK_API_VERSION_1_0) { + return PAL_MAKE_SHADER_TARGET(1, 2); + } + + return 0; +} + +void PAL_CALL enumerateFormatsVk( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats) +{ + int32_t fmtCount = 0; + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + VkFormatProperties props = {0}; + + for (int i = 0; i < PAL_FORMAT_COUNT; i++) { + VkFormat fmt = formatToVk((PalFormat)i); + s_Vk.getPhysicalDeviceFormatProperties(phyDevice, fmt, &props); + if (props.optimalTilingFeatures != 0) { + // format supported + if (outFormats) { + if (fmtCount < *count) { + PalFormatInfo* fmtInfo = &outFormats[fmtCount++]; + fmtInfo->format = (PalFormat)i; + fmtInfo->usages = imageUsageFromVk(props.optimalTilingFeatures); + } + + } else { + fmtCount++; + } + } + } + if (!outFormats) { + *count = fmtCount; + } +} + +PalBool PAL_CALL isFormatSupportedVk( + PalAdapter* adapter, + PalFormat format) +{ + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + VkFormatProperties props = {0}; + + VkFormat fmt = formatToVk(format); + s_Vk.getPhysicalDeviceFormatProperties(phyDevice, fmt, &props); + if (props.optimalTilingFeatures != 0) { + return PAL_TRUE; + } + return PAL_FALSE; +} + +PalImageUsages PAL_CALL queryFormatImageUsagesVk( + PalAdapter* adapter, + PalFormat format) +{ + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + VkFormatProperties props = {0}; + + VkFormat fmt = formatToVk(format); + s_Vk.getPhysicalDeviceFormatProperties(phyDevice, fmt, &props); + if (props.optimalTilingFeatures == 0) { + return PAL_IMAGE_USAGE_UNDEFINED; + } + + return imageUsageFromVk(props.optimalTilingFeatures); +} + +PalSampleCount PAL_CALL queryFormatSampleCountVk( + PalAdapter* adapter, + PalFormat format) +{ + VkResult result; + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkFormatProperties props = {0}; + VkImageFormatProperties formatProps = {0}; + + VkFormat fmt = formatToVk(format); + s_Vk.getPhysicalDeviceFormatProperties(adapterImpl->handle, fmt, &props); + if (props.optimalTilingFeatures == 0) { + return PAL_SAMPLE_COUNT_1; + } + + VkImageUsageFlags imageUsageImpl = 0; + PalImageUsages imageUsages = imageUsageFromVk(props.optimalTilingFeatures); + PalBool isDepth = (imageUsages & PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT) != 0; + if (isDepth) { + imageUsageImpl = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } else { + imageUsageImpl = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + result = s_Vk.getPhysicalDeviceImageFormatProperties( + adapterImpl->handle, + fmt, + VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + imageUsageImpl, + 0, + &formatProps); + + if (result != VK_SUCCESS) { + return PAL_SAMPLE_COUNT_1; + } + + return samplesFromVk(formatProps.sampleCounts); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_as_vulkan.c b/src/graphics/vulkan/pal_as_vulkan.c new file mode 100644 index 00000000..177c9584 --- /dev/null +++ b/src/graphics/vulkan/pal_as_vulkan.c @@ -0,0 +1,107 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +PalResult PAL_CALL createAccelerationstructureVk( + PalDevice* device, + const PalAccelerationStructureCreateInfo* info, + PalAccelerationStructure** outAs) +{ + VkResult result; + AccelerationStructureVk* as = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + BufferVk* buffer = (BufferVk*)info->buffer; + + as = palAllocate(s_Vk.allocator, sizeof(AccelerationStructureVk), 0); + if (!as) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkAccelerationStructureCreateInfoKHR createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + createInfo.offset = (VkDeviceSize)info->offset; + createInfo.size = (VkDeviceSize)info->size; + createInfo.buffer = buffer->handle; + createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL) { + createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + } + + result = deviceImpl->createAccelerationStructure( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &as->handle); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // get and cache address + VkAccelerationStructureDeviceAddressInfoKHR addressInfo = {0}; + addressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR; + addressInfo.accelerationStructure = as->handle; + as->address = deviceImpl->getAccelerationDeviceAddress(deviceImpl->handle, &addressInfo); + + as->device = deviceImpl; + *outAs = (PalAccelerationStructure*)as; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyAccelerationstructureVk(PalAccelerationStructure* as) +{ + AccelerationStructureVk* asImpl = (AccelerationStructureVk*)as; + asImpl->device->destroyAccelerationStructure( + asImpl->device->handle, + asImpl->handle, + &s_Vk.allocatorImpl); + + palFree(s_Vk.allocator, asImpl); +} + +void PAL_CALL getAccelerationStructureBuildSizeVk( + PalDevice* device, + PalAccelerationStructureBuildInfo* info, + PalAccelerationStructureBuildSize* size) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkAccelerationStructureGeometryKHR* geometries = nullptr; + uint32_t* maxPrimities = nullptr; + VkAccelerationStructureBuildGeometryInfoKHR buildInfo = {0}; + uint32_t geometriesSize = sizeof(VkAccelerationStructureGeometryKHR) * info->count; + + geometries = palAllocate(s_Vk.allocator, geometriesSize, 0); + maxPrimities = palAllocate(s_Vk.allocator, sizeof(uint32_t) * info->count, 0); + if (!maxPrimities || !geometries) { + return; + } + + memset(geometries, 0, geometriesSize); + memset(maxPrimities, 0, sizeof(uint32_t) * info->count); + fillBuildInfoVk(PAL_TRUE, info, geometries, &buildInfo, maxPrimities); + + VkAccelerationStructureBuildSizesInfoKHR sizeInfo = {0}; + sizeInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + deviceImpl->getAccelerationBuildsize( + deviceImpl->handle, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &buildInfo, + maxPrimities, + &sizeInfo); + + size->accelerationStructureSize = sizeInfo.accelerationStructureSize; + size->scratchBufferSize = sizeInfo.buildScratchSize; + size->updateScratchBufferSize = sizeInfo.updateScratchSize; + + palFree(s_Vk.allocator, geometries); + palFree(s_Vk.allocator, maxPrimities); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_buffer_vulkan.c b/src/graphics/vulkan/pal_buffer_vulkan.c new file mode 100644 index 00000000..50f3a09c --- /dev/null +++ b/src/graphics/vulkan/pal_buffer_vulkan.c @@ -0,0 +1,463 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +static VkBufferUsageFlags bufferUsageToVk(PalBufferUsages usages) +{ + VkBufferUsageFlags flags = 0; + if (usages & PAL_BUFFER_USAGE_VERTEX) { + flags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + } + + if (usages & PAL_BUFFER_USAGE_INDEX) { + flags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + } + + if (usages & PAL_BUFFER_USAGE_UNIFORM) { + flags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + } + + if (usages & PAL_BUFFER_USAGE_STORAGE) { + flags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + } + + if (usages & PAL_BUFFER_USAGE_TRANSFER_SRC) { + flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + } + + if (usages & PAL_BUFFER_USAGE_TRANSFER_DST) { + flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + } + + if (usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE) { + flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR; + } + + if (usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH) { + flags |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + } + + if (usages & PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_READ_ONLY_INPUT) { + flags |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR; + } + + if (usages & PAL_BUFFER_USAGE_DEVICE_ADDRESS) { + flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + } + + if (usages & PAL_BUFFER_USAGE_INDIRECT) { + flags |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; + } + + return flags; +} + +static uint32_t getFormatSize(PalFormat format) +{ + switch (format) { + case PAL_FORMAT_R8_UNORM: + case PAL_FORMAT_R8_SNORM: + case PAL_FORMAT_R8_UINT: + case PAL_FORMAT_R8_SINT: + case PAL_FORMAT_R8_SRGB: + case PAL_FORMAT_S8_UINT: + return 1; + + case PAL_FORMAT_R16_UNORM: + case PAL_FORMAT_R16_SNORM: + case PAL_FORMAT_R16_UINT: + case PAL_FORMAT_R16_SINT: + case PAL_FORMAT_R16_SFLOAT: + case PAL_FORMAT_R8G8_UNORM: + case PAL_FORMAT_R8G8_SNORM: + case PAL_FORMAT_R8G8_UINT: + case PAL_FORMAT_R8G8_SINT: + case PAL_FORMAT_R8G8_SRGB: + case PAL_FORMAT_D16_UNORM: + return 2; + + case PAL_FORMAT_R8G8B8_UNORM: + case PAL_FORMAT_R8G8B8_SNORM: + case PAL_FORMAT_R8G8B8_UINT: + case PAL_FORMAT_R8G8B8_SINT: + case PAL_FORMAT_R8G8B8_SRGB: + case PAL_FORMAT_B8G8R8_UNORM: + case PAL_FORMAT_B8G8R8_SNORM: + case PAL_FORMAT_B8G8R8_UINT: + case PAL_FORMAT_B8G8R8_SINT: + case PAL_FORMAT_B8G8R8_SRGB: + case PAL_FORMAT_D16_UNORM_S8_UINT: + return 3; + + case PAL_FORMAT_R32_UINT: + case PAL_FORMAT_R32_SINT: + case PAL_FORMAT_R32_SFLOAT: + case PAL_FORMAT_R16G16_UNORM: + case PAL_FORMAT_R16G16_SNORM: + case PAL_FORMAT_R16G16_UINT: + case PAL_FORMAT_R16G16_SINT: + case PAL_FORMAT_R16G16_SFLOAT: + case PAL_FORMAT_R8G8B8A8_UNORM: + case PAL_FORMAT_R8G8B8A8_SNORM: + case PAL_FORMAT_R8G8B8A8_UINT: + case PAL_FORMAT_R8G8B8A8_SINT: + case PAL_FORMAT_R8G8B8A8_SRGB: + case PAL_FORMAT_B8G8R8A8_UNORM: + case PAL_FORMAT_B8G8R8A8_SNORM: + case PAL_FORMAT_B8G8R8A8_UINT: + case PAL_FORMAT_B8G8R8A8_SINT: + case PAL_FORMAT_B8G8R8A8_SRGB: + case PAL_FORMAT_D32_SFLOAT: + case PAL_FORMAT_D24_UNORM_S8_UINT: + return 4; + + case PAL_FORMAT_D32_SFLOAT_S8_UINT: + return 5; + + case PAL_FORMAT_R16G16B16_UNORM: + case PAL_FORMAT_R16G16B16_SNORM: + case PAL_FORMAT_R16G16B16_UINT: + case PAL_FORMAT_R16G16B16_SINT: + case PAL_FORMAT_R16G16B16_SFLOAT: + return 6; + + case PAL_FORMAT_R64_UINT: + case PAL_FORMAT_R64_SINT: + case PAL_FORMAT_R64_SFLOAT: + case PAL_FORMAT_R32G32_UINT: + case PAL_FORMAT_R32G32_SINT: + case PAL_FORMAT_R32G32_SFLOAT: + case PAL_FORMAT_R16G16B16A16_UNORM: + case PAL_FORMAT_R16G16B16A16_SNORM: + case PAL_FORMAT_R16G16B16A16_UINT: + case PAL_FORMAT_R16G16B16A16_SINT: + case PAL_FORMAT_R16G16B16A16_SFLOAT: + return 8; + + case PAL_FORMAT_R32G32B32_UINT: + case PAL_FORMAT_R32G32B32_SINT: + case PAL_FORMAT_R32G32B32_SFLOAT: + return 12; + + case PAL_FORMAT_R64G64_UINT: + case PAL_FORMAT_R64G64_SINT: + case PAL_FORMAT_R64G64_SFLOAT: + case PAL_FORMAT_R32G32B32A32_UINT: + case PAL_FORMAT_R32G32B32A32_SINT: + case PAL_FORMAT_R32G32B32A32_SFLOAT: + return 16; + + case PAL_FORMAT_R64G64B64_UINT: + case PAL_FORMAT_R64G64B64_SINT: + case PAL_FORMAT_R64G64B64_SFLOAT: + return 24; + + case PAL_FORMAT_R64G64B64A64_UINT: + case PAL_FORMAT_R64G64B64A64_SINT: + case PAL_FORMAT_R64G64B64A64_SFLOAT: + return 32; + } + + return 0; +} + +static VkGeometryInstanceFlagsKHR instanceFlagsToVk(PalAccelerationStructureInstanceFlags flags) +{ + VkGeometryInstanceFlagsKHR instanceFlags = 0; + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_OPAQUE) { + instanceFlags |= VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_FORCE_NO_OPAQUE) { + instanceFlags |= VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FACING_CULL_DISABLE) { + instanceFlags |= VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + } + + if (flags & PAL_ACCELERATION_STRUCTURE_INSTANCE_FLAG_TRIANGLE_FRONT_COUNTERCLOCKWISE) { + instanceFlags |= VK_GEOMETRY_INSTANCE_TRIANGLE_FRONT_COUNTERCLOCKWISE_BIT_KHR; + } + + return instanceFlags; +} + +PalResult PAL_CALL createBufferVk( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer) +{ + VkResult result; + BufferVk* buffer = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + MemoryVk* memory = nullptr; + + buffer = palAllocate(s_Vk.allocator, sizeof(BufferVk), 0); + if (!buffer) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (info->memoryUsage != PAL_BUFFER_MEMORY_USAGE_MANUAL) { + memory = palAllocate(s_Vk.allocator, sizeof(MemoryVk), 0); + if (!memory) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + VkBufferCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.size = info->size; + createInfo.usage = bufferUsageToVk(info->usages); + + result = + s_Vk.createBuffer(deviceImpl->handle, &createInfo, &s_Vk.allocatorImpl, &buffer->handle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + buffer->isMemoryManaged = PAL_FALSE; + if (info->memoryUsage != PAL_BUFFER_MEMORY_USAGE_MANUAL) { + PalMemoryType memoryType = PAL_MEMORY_TYPE_GPU_ONLY; + if (info->memoryUsage == PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD) { + memoryType = PAL_MEMORY_TYPE_CPU_UPLOAD; + + } else if (info->memoryUsage == PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK) { + memoryType = PAL_MEMORY_TYPE_CPU_READBACK; + } + + // allocate and manage memory + VkMemoryRequirements memReq = {0}; + s_Vk.getBufferMemoryRequirements(deviceImpl->handle, buffer->handle, &memReq); + + VkMemoryAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = (VkDeviceSize)memReq.size; + VkMemoryAllocateFlagsInfo allocateFlagsInfo = {0}; + + uint32_t memoryMask = deviceImpl->memoryClassMask[memoryType] & memReq.memoryTypeBits; + uint32_t memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, memoryMask); + if (!(memoryMask & (1u << memoryIndex))) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + allocateInfo.memoryTypeIndex = memoryIndex; + if (info->usages & PAL_BUFFER_USAGE_DEVICE_ADDRESS) { + allocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + allocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + allocateInfo.pNext = &allocateFlagsInfo; + } + + result = s_Vk.allocateMemory( + deviceImpl->handle, + &allocateInfo, + &s_Vk.allocatorImpl, + &memory->handle); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + result = s_Vk.bindBufferMemory(deviceImpl->handle, buffer->handle, memory->handle, 0); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + memory->type = memoryType; + buffer->isMemoryManaged = PAL_TRUE; + } + + buffer->memory = memory; + buffer->usages = info->usages; + buffer->device = deviceImpl; + *outBuffer = (PalBuffer*)buffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyBufferVk(PalBuffer* buffer) +{ + BufferVk* bufferImpl = (BufferVk*)buffer; + s_Vk.destroyBuffer(bufferImpl->device->handle, bufferImpl->handle, &s_Vk.allocatorImpl); + if (bufferImpl->isMemoryManaged) { + s_Vk.freeMemory( + bufferImpl->device->handle, + bufferImpl->memory->handle, + &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, bufferImpl->memory); + } + palFree(s_Vk.allocator, buffer); +} + +void PAL_CALL getBufferMemoryRequirementsVk( + PalBuffer* buffer, + PalMemoryRequirements* requirements) +{ + BufferVk* bufferImpl = (BufferVk*)buffer; + DeviceVk* device = bufferImpl->device; + VkMemoryRequirements memReq = {0}; + s_Vk.getBufferMemoryRequirements(device->handle, bufferImpl->handle, &memReq); + + requirements->alignment = (uint64_t)memReq.alignment; + requirements->size = (uint64_t)memReq.size; + requirements->memoryMask = palPackUint32(memReq.memoryTypeBits, bufferImpl->usages); + requirements->supportedMemoryTypes = 0; + + if ((memReq.memoryTypeBits & device->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY]) != 0) { + requirements->supportedMemoryTypes |= (1u << PAL_MEMORY_TYPE_GPU_ONLY); + } + + if ((memReq.memoryTypeBits & device->memoryClassMask[PAL_MEMORY_TYPE_CPU_UPLOAD]) != 0) { + requirements->supportedMemoryTypes |= (1u << PAL_MEMORY_TYPE_CPU_UPLOAD); + } + + if ((memReq.memoryTypeBits & device->memoryClassMask[PAL_MEMORY_TYPE_CPU_READBACK]) != 0) { + requirements->supportedMemoryTypes |= (1u << PAL_MEMORY_TYPE_CPU_READBACK); + } +} + +void PAL_CALL computeInstanceStagingSizeVk( + PalDevice* device, + uint32_t instanceCount, + uint64_t* outSize) +{ + *outSize = sizeof(VkAccelerationStructureInstanceKHR) * instanceCount; +} + +void PAL_CALL computeImageStagingRequirementsVk( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements) +{ + uint32_t imageFormatSize = getFormatSize(imageFormat); + uint32_t length = 0; + uint32_t height = 0; + length = copyInfo->bufferRowLength ? copyInfo->bufferRowLength : copyInfo->imageWidth; + height = copyInfo->bufferImageHeight ? copyInfo->bufferImageHeight : copyInfo->imageHeight; + uint32_t rowPitch = length * imageFormatSize; + + requirements->bufferRowLength = length; + requirements->bufferImageHeight = height; + requirements->bufferSize = (uint64_t)rowPitch * length * copyInfo->imageDepth; +} + +void PAL_CALL writeInstanceStagingVk( + PalDevice* device, + uint32_t instanceCount, + PalAccelerationStructureInstance* instances, + void* ptr) +{ + VkAccelerationStructureInstanceKHR* data = ptr; + for (int i = 0; i < instanceCount; i++) { + PalAccelerationStructureInstance* src = &instances[i]; + VkAccelerationStructureInstanceKHR* dst = &data[i]; + AccelerationStructureVk* as = (AccelerationStructureVk*)src->blas; + + dst->mask = src->mask & 0xFF; + dst->instanceCustomIndex = src->instanceId & 0xFFFFFF; + dst->accelerationStructureReference = as->address; + dst->instanceShaderBindingTableRecordOffset = src->hitGroupOffset & 0xFFFFFF; + dst->flags = instanceFlagsToVk(src->flags); + memcpy(dst->transform.matrix, src->transform, sizeof(float) * 12); + } +} + +void PAL_CALL writeImageStagingVk( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr) +{ + uint32_t imageFormatSize = getFormatSize(imageFormat); + uint32_t dstRowPitch = copyInfo->bufferRowLength * imageFormatSize; + uint32_t srcRowPitch = copyInfo->imageWidth * imageFormatSize; + const uint32_t dstSlicePitch = dstRowPitch * copyInfo->bufferImageHeight; + const uint32_t srcSlicePitch = srcRowPitch * copyInfo->imageHeight; + + // manually offset the buffer with the provided offset + uint8_t* dst = (uint8_t*)ptr + copyInfo->bufferOffset; + const uint8_t* src = (const uint8_t*)srcData; + + // write to destination pointer + for (uint32_t z = 0; z < copyInfo->imageDepth; z++) { + for (uint32_t y = 0; y < copyInfo->imageHeight; y++) { + memcpy( + dst + z * dstSlicePitch + y * dstRowPitch, + src + z * srcSlicePitch + y * srcRowPitch, + srcRowPitch); + } + } +} + +PalResult PAL_CALL bindBufferMemoryVk( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset) +{ + VkResult result; + MemoryVk* memoryImpl = (MemoryVk*)memory; + BufferVk* bufferImpl = (BufferVk*)buffer; + + if (bufferImpl->memory) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + result = s_Vk.bindBufferMemory( + bufferImpl->device->handle, + bufferImpl->handle, + memoryImpl->handle, + offset); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + bufferImpl->memory = memoryImpl; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL mapBufferVk( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr) +{ + VkResult result; + BufferVk* bufferImpl = (BufferVk*)buffer; + DeviceVk* device = bufferImpl->device; + + result = s_Vk.mapMemory(device->handle, bufferImpl->memory->handle, offset, size, 0, outPtr); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL unmapBufferVk(PalBuffer* buffer) +{ + BufferVk* bufferImpl = (BufferVk*)buffer; + s_Vk.unmapMemory(bufferImpl->device->handle, bufferImpl->memory->handle); +} + +PalDeviceAddress PAL_CALL getBufferDeviceAddressVk(PalBuffer* buffer) +{ + BufferVk* bufferImpl = (BufferVk*)buffer; + if (!(bufferImpl->usages & PAL_BUFFER_USAGE_DEVICE_ADDRESS)) { + return 0; + } + + VkBufferDeviceAddressInfoKHR bufferInfo = {0}; + bufferInfo.buffer = bufferImpl->handle; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR; + return bufferImpl->device->getBufferrAddress(bufferImpl->device->handle, &bufferInfo); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_command_pool_vulkan.c b/src/graphics/vulkan/pal_command_pool_vulkan.c new file mode 100644 index 00000000..227ed37a --- /dev/null +++ b/src/graphics/vulkan/pal_command_pool_vulkan.c @@ -0,0 +1,233 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +PalResult PAL_CALL createCommandPoolVk( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool) +{ + VkResult result; + CommandPoolVk* pool = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + QueueVk* queueImpl = (QueueVk*)queue; + PhysicalQueue* phyQueue = queueImpl->phyQueue; + + pool = palAllocate(s_Vk.allocator, sizeof(CommandPoolVk), 0); + if (!pool) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkCommandPoolCreateInfo cInfo = {0}; + cInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + cInfo.queueFamilyIndex = phyQueue->familyIndex; + cInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + result = s_Vk.createCommandPool(deviceImpl->handle, &cInfo, &s_Vk.allocatorImpl, &pool->handle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + pool->device = deviceImpl; + *outPool = (PalCommandPool*)pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyCommandPoolVk(PalCommandPool* pool) +{ + CommandPoolVk* cmdPoolImpl = (CommandPoolVk*)pool; + s_Vk.destroyCommandPool(cmdPoolImpl->device->handle, cmdPoolImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, cmdPoolImpl); +} + +PalResult PAL_CALL allocateCommandBufferVk( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer) +{ + VkResult result; + CommandBufferVk* cmdBuffer = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + CommandPoolVk* cmdPoolImpl = (CommandPoolVk*)pool; + + cmdBuffer = palAllocate(s_Vk.allocator, sizeof(CommandBufferVk), 0); + if (!cmdBuffer) { + PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // allocate memory for the linear allocator. We first start with 4KB + cmdBuffer->allocator.memory = (uint8_t*)palAllocate(s_Vk.allocator, 4096, 0); + if (!cmdBuffer->allocator.memory) { + PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + cmdBuffer->allocator.size = 4096; + cmdBuffer->allocator.offset = 0; + + VkCommandBufferAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocateInfo.commandBufferCount = 1; + allocateInfo.commandPool = cmdPoolImpl->handle; + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + + cmdBuffer->primary = PAL_TRUE; + if (type == PAL_COMMAND_BUFFER_TYPE_SECONDARY) { + allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; + cmdBuffer->primary = PAL_FALSE; + } + + result = s_Vk.allocateCommandBuffer(deviceImpl->handle, &allocateInfo, &cmdBuffer->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, (void*)cmdBuffer->allocator.memory); + palFree(s_Vk.allocator, cmdBuffer); + return makeResultVk(result); + } + + // create tmp buffer + VkBufferCreateInfo bufCreateInfo = {0}; + bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufCreateInfo.size = sizeof(VkTraceRaysIndirectCommandKHR); + bufCreateInfo.usage = VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; + bufCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + result = s_Vk.createBuffer( + deviceImpl->handle, + &bufCreateInfo, + &s_Vk.allocatorImpl, + &cmdBuffer->buffer); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // allocate CPU upload memory and bind + VkMemoryRequirements memReq = {0}; + s_Vk.getBufferMemoryRequirements(deviceImpl->handle, cmdBuffer->buffer, &memReq); + + VkMemoryAllocateInfo bufferAllocateInfo = {0}; + bufferAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + bufferAllocateInfo.allocationSize = memReq.size; + + uint32_t mask = deviceImpl->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY] & memReq.memoryTypeBits; + uint32_t memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, mask); + bufferAllocateInfo.memoryTypeIndex = memoryIndex; + + result = s_Vk.allocateMemory( + deviceImpl->handle, + &bufferAllocateInfo, + &s_Vk.allocatorImpl, + &cmdBuffer->bufferMemory); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + s_Vk.bindBufferMemory(deviceImpl->handle, cmdBuffer->buffer, cmdBuffer->bufferMemory, 0); + + cmdBuffer->device = deviceImpl; + cmdBuffer->pool = cmdPoolImpl; + *outCmdBuffer = (PalCommandBuffer*)cmdBuffer; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL freeCommandBufferVk(PalCommandBuffer* cmdBuffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + s_Vk.freeCommandBuffer( + cmdBufferImpl->device->handle, + cmdBufferImpl->pool->handle, + 1, + &cmdBufferImpl->handle); + + s_Vk.destroyBuffer(cmdBufferImpl->device->handle, cmdBufferImpl->buffer, &s_Vk.allocatorImpl); + s_Vk.freeMemory( + cmdBufferImpl->device->handle, + cmdBufferImpl->bufferMemory, + &s_Vk.allocatorImpl); + + palFree(s_Vk.allocator, (void*)cmdBufferImpl->allocator.memory); + palFree(s_Vk.allocator, cmdBufferImpl); +} + +PalResult PAL_CALL resetCommandBufferVk(PalCommandBuffer* cmdBuffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkResult result = s_Vk.resetCommandBuffer(cmdBufferImpl->handle, 0); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL submitCommandBufferVk( + PalQueue* queue, + PalCommandBufferSubmitInfo* info) +{ + VkResult result; + int32_t waitSemaphoreCount = 0; + int32_t signalSemaphoreCount = 0; + VkFence fenceHandle = nullptr; + VkSemaphore waitSemaphoreHandle = nullptr; + VkSemaphore signalSemaphoreHandle = nullptr; + QueueVk* queueImpl = (QueueVk*)queue; + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)info->cmdBuffer; + PhysicalQueue* phyQueue = queueImpl->phyQueue; + + if (info->waitSemaphore) { + SemaphoreVk* tmp = (SemaphoreVk*)info->waitSemaphore; + waitSemaphoreHandle = tmp->handle; + waitSemaphoreCount = 1; + } + + if (info->signalSemaphore) { + SemaphoreVk* tmp = (SemaphoreVk*)info->signalSemaphore; + signalSemaphoreHandle = tmp->handle; + signalSemaphoreCount = 1; + } + + if (info->fence) { + FenceVk* tmp = (FenceVk*)info->fence; + fenceHandle = tmp->handle; + } + + VkCommandBufferSubmitInfoKHR cmdBufferSubmitInfo = {0}; + cmdBufferSubmitInfo.commandBuffer = cmdBufferImpl->handle; + cmdBufferSubmitInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO_KHR; + + VkSemaphoreSubmitInfoKHR waitSubmitInfo = {0}; + waitSubmitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR; + waitSubmitInfo.semaphore = waitSemaphoreHandle; + waitSubmitInfo.stageMask = pipelineStagesToVk(info->waitStages); + waitSubmitInfo.value = info->waitValue; + + VkSemaphoreSubmitInfoKHR signalSubmitInfo = {0}; + signalSubmitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO_KHR; + signalSubmitInfo.semaphore = signalSemaphoreHandle; + signalSubmitInfo.stageMask = pipelineStagesToVk(info->signalStages); + signalSubmitInfo.value = info->signalValue; + + VkSubmitInfo2KHR submitInfo = {0}; + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR; + submitInfo.commandBufferInfoCount = 1; + submitInfo.pCommandBufferInfos = &cmdBufferSubmitInfo; + submitInfo.pSignalSemaphoreInfos = &signalSubmitInfo; + submitInfo.pWaitSemaphoreInfos = &waitSubmitInfo; + submitInfo.waitSemaphoreInfoCount = waitSemaphoreCount; + submitInfo.signalSemaphoreInfoCount = signalSemaphoreCount; + + result = cmdBufferImpl->device->queueSubmit(phyQueue->handle, 1, &submitInfo, fenceHandle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_commands_vulkan.c b/src/graphics/vulkan/pal_commands_vulkan.c new file mode 100644 index 00000000..9c74eada --- /dev/null +++ b/src/graphics/vulkan/pal_commands_vulkan.c @@ -0,0 +1,1292 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +typedef struct { + VkAccessFlags2 access; + VkImageLayout layout; +} BarrierInfo; + +static void commitShaderbindingTableUpdate( + CommandBufferVk* cmdBuffer, + ShaderBindingTableVk* sbt) +{ + if (!sbt->isDirty) { + return; + } + + // begin upload buffer copy to gpu buffer + VkBufferCopy copyRegion = {0}; + copyRegion.size = sbt->stagingBufferSize; + s_Vk.cmdCopyBuffer(cmdBuffer->handle, sbt->stagingBuffer, sbt->buffer, 1, ©Region); + + // put a memory barrier + VkBufferMemoryBarrier2KHR barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + + barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT_KHR; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR; + + barrier.dstStageMask = VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR; + + barrier.buffer = sbt->buffer; + barrier.offset = 0; + barrier.size = VK_WHOLE_SIZE; + + VkDependencyInfo dependencyInfo = {0}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = 1; + dependencyInfo.pBufferMemoryBarriers = &barrier; + + cmdBuffer->device->cmdPipelineBarrier(cmdBuffer->handle, &dependencyInfo); + sbt->isDirty = PAL_FALSE; +} + +static BarrierInfo barrierToVk(PalUsageState state) +{ + BarrierInfo barrier = {0}; + switch (state) { + case PAL_USAGE_STATE_PRESENT: { + barrier.access = 0; + barrier.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + return barrier; + } + + case PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE: { + barrier.access = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_DEPTH_ATTACHMENT_READ: { + barrier.access = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_DEPTH_ATTACHMENT_WRITE: { + barrier.access = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_STENCIL_ATTACHMENT_READ: { + barrier.access = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_STENCIL_ATTACHMENT_WRITE: { + barrier.access = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_FRAGMENT_SHADING_RATE_ATTACHMENT_READ: { + barrier.access = VK_ACCESS_2_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + return barrier; + } + + case PAL_USAGE_STATE_TRANSFER_READ: { + barrier.access = VK_ACCESS_2_TRANSFER_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_TRANSFER_WRITE: { + barrier.access = VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_VERTEX_READ: { + barrier.access = VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_INDEX_READ: { + barrier.access = VK_ACCESS_2_INDEX_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_INDIRECT_READ: { + barrier.access = VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_UNIFORM_READ: { + barrier.access = VK_ACCESS_2_UNIFORM_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_SHADER_READ: { + barrier.access = VK_ACCESS_2_SHADER_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + return barrier; + } + + case PAL_USAGE_STATE_SHADER_WRITE: { + barrier.access = VK_ACCESS_2_SHADER_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_GENERAL; + return barrier; + } + + case PAL_USAGE_STATE_STORAGE_READ: { + barrier.access = VK_ACCESS_2_SHADER_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_GENERAL; + return barrier; + } + + case PAL_USAGE_STATE_STORAGE_WRITE: { + barrier.access = VK_ACCESS_2_SHADER_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_GENERAL; + return barrier; + } + + case PAL_USAGE_STATE_HOST_READ: { + barrier.access = VK_ACCESS_2_HOST_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_HOST_WRITE: { + barrier.access = VK_ACCESS_2_HOST_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ: { + barrier.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + + case PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE: { + barrier.access = VK_ACCESS_2_ACCELERATION_STRUCTURE_WRITE_BIT_KHR; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; + } + } + + barrier.access = 0; + barrier.layout = VK_IMAGE_LAYOUT_UNDEFINED; + return barrier; +} + +static VkRenderingFlags renderingFlagToVk(PalRenderingFlags flags) +{ + VkRenderingFlags renderingFlags = 0; + if (flags == PAL_RENDERING_FLAG_NONE) { + renderingFlags = 0; + } + + if (flags & PAL_RENDERING_FLAG_RESUMING) { + renderingFlags |= VK_RENDERING_RESUMING_BIT; + } + + if (flags & PAL_RENDERING_FLAG_SUSPENDING) { + renderingFlags |= VK_RENDERING_SUSPENDING_BIT; + } + + return renderingFlags; +} + +static VkResolveModeFlags resolveModeToVk(PalResolveMode mode) +{ + switch (mode) { + case PAL_RESOLVE_MODE_SAMPLE_ZERO: + return VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR; + + case PAL_RESOLVE_MODE_AVERAGE: + return VK_RESOLVE_MODE_AVERAGE_BIT_KHR; + + case PAL_RESOLVE_MODE_MIN: + return VK_RESOLVE_MODE_MIN_BIT_KHR; + + case PAL_RESOLVE_MODE_MAX: + return VK_RESOLVE_MODE_MAX_BIT_KHR; + } + + return VK_RESOLVE_MODE_NONE_KHR; +} + +PalResult PAL_CALL cmdBeginVk( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkCommandBufferBeginInfo beginInfo = {0}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + VkCommandBufferInheritanceInfo inheritanceInfo = {0}; + inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; + VkCommandBufferInheritanceRenderingInfoKHR layout = {0}; + layout.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDERING_INFO_KHR; + + cmdBufferImpl->allocator.offset = 0; // reset + VkFormat format = VK_FORMAT_UNDEFINED; + VkFormat* colorAttachments = nullptr; + + if (!cmdBufferImpl->primary) { + // secondary command buffer + colorAttachments = palLinearAlloc( + &cmdBufferImpl->allocator, + sizeof(VkFormat) * info->colorAttachentCount, + 0); + + if (!colorAttachments) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + for (int i = 0; i < info->colorAttachentCount; i++) { + format = formatToVk(info->colorAttachmentsFormat[i]); + colorAttachments[i] = format; + } + layout.colorAttachmentCount = info->colorAttachentCount; + layout.pColorAttachmentFormats = colorAttachments; + + // depth stencil attachment + format = formatToVk(info->depthStencilAttachmentFormat); + layout.depthAttachmentFormat = format; + layout.stencilAttachmentFormat = format; + + layout.rasterizationSamples = samplesToVk(info->sampleCount); + layout.flags = renderingFlagToVk(info->flags); + if (info->viewCount == 1) { + layout.viewMask = 0; + } else { + layout.viewMask = (1 << info->viewCount) - 1; + } + + inheritanceInfo.pNext = &layout; + beginInfo.pNext = &inheritanceInfo; + } + + VkResult result = s_Vk.cmdBegin(cmdBufferImpl->handle, &beginInfo); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL cmdEndVk(PalCommandBuffer* cmdBuffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkResult result = s_Vk.cmdEnd(cmdBufferImpl->handle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL cmdExecuteCommandBufferVk( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer) +{ + CommandBufferVk* primaryCmdBufferImpl = (CommandBufferVk*)primaryCmdBuffer; + CommandBufferVk* secondaryCmdBufferImpl = (CommandBufferVk*)secondaryCmdBuffer; + s_Vk.cmdExecuteCommandBuffer(primaryCmdBufferImpl->handle, 1, &secondaryCmdBufferImpl->handle); + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL cmdSetFragmentShadingRateVk( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + VkExtent2D size = getShadingRateSizeVk(state->rate); + VkFragmentShadingRateCombinerOpKHR combinerOps[2]; + for (int i = 0; i < 2; i++) { + combinerOps[i] = combinerOpsToVk(state->combinerOps[i]); + } + + device->cmdSetFragmentShadingRate(cmdBufferImpl->handle, &size, combinerOps); +} + +void PAL_CALL cmdDrawMeshTasksVk( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + device->cmdDrawMeshTask(cmdBufferImpl->handle, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL cmdDrawMeshTasksIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + BufferVk* bufferImpl = (BufferVk*)buffer; + + uint32_t stride = sizeof(VkDrawMeshTasksIndirectCommandEXT); + device + ->cmdDrawMeshTaskIndirect(cmdBufferImpl->handle, bufferImpl->handle, 0, drawCount, stride); +} + +void PAL_CALL cmdDrawMeshTasksIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + BufferVk* bufferImpl = (BufferVk*)buffer; + BufferVk* countBufferImpl = (BufferVk*)countBuffer; + + uint32_t stride = sizeof(VkDrawMeshTasksIndirectCommandEXT); + device->cmdDrawMeshTaskIndirectCount( + cmdBufferImpl->handle, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0, + maxDrawCount, + stride); +} + +void PAL_CALL cmdBuildAccelerationStructureVk( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + + VkAccelerationStructureGeometryKHR* geometries = nullptr; + VkAccelerationStructureBuildRangeInfoKHR* rangeInfos = nullptr; + const VkAccelerationStructureBuildRangeInfoKHR** tmpRangeInfos = nullptr; + VkAccelerationStructureBuildGeometryInfoKHR buildInfo = {0}; + + tmpRangeInfos = palLinearAlloc(&cmdBufferImpl->allocator, sizeof(void*) * info->count, 0); + + geometries = palLinearAlloc( + &cmdBufferImpl->allocator, + sizeof(VkAccelerationStructureGeometryKHR) * info->count, + 0); + + rangeInfos = palLinearAlloc( + &cmdBufferImpl->allocator, + sizeof(VkAccelerationStructureBuildRangeInfoKHR) * info->count, + 0); + + memset(geometries, 0, sizeof(VkAccelerationStructureGeometryKHR) * info->count); + memset(rangeInfos, 0, sizeof(VkAccelerationStructureBuildRangeInfoKHR) * info->count); + fillBuildInfoVk(PAL_FALSE, info, geometries, &buildInfo, rangeInfos); + + for (int i = 0; i < info->count; i++) { + tmpRangeInfos[i] = &rangeInfos[i]; + } + + cmdBufferImpl->device + ->cmdBuildAccelerationStructures(cmdBufferImpl->handle, 1, &buildInfo, tmpRangeInfos); +} + +void PAL_CALL cmdBeginRenderingVk( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkRenderingInfoKHR rendering = {0}; + rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR; + + VkRenderingAttachmentInfoKHR depthAttachment = {0}; + VkRenderingAttachmentInfoKHR stencilAttachment = {0}; + + VkRenderingAttachmentInfoKHR* colorAttachments = nullptr; + colorAttachments = palLinearAlloc( + &cmdBufferImpl->allocator, + sizeof(VkRenderingAttachmentInfoKHR) * info->colorAttachentCount, + 0); + + VkRenderingAttachmentInfoKHR* attachment = nullptr; + PalAttachmentDesc* desc = nullptr; + ImageViewVk* imageView = nullptr; + ImageViewVk* resolveImageView = nullptr; + VkImageLayout layout = VK_IMAGE_LAYOUT_GENERAL; + + VkRenderingFragmentShadingRateAttachmentInfoKHR fsrInfo = {0}; + fsrInfo.sType = VK_STRUCTURE_TYPE_RENDERING_FRAGMENT_SHADING_RATE_ATTACHMENT_INFO_KHR; + + for (int i = 0; i < info->colorAttachentCount; i++) { + attachment = &colorAttachments[i]; + desc = &info->colorAttachments[i]; + imageView = (ImageViewVk*)desc->imageView; + resolveImageView = (ImageViewVk*)desc->resolveImageView; + + attachment->sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + attachment->pNext = nullptr; + layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + attachment->resolveImageView = nullptr; + attachment->resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + attachment->clearValue.color.float32[0] = desc->clearValue.color[0]; + attachment->clearValue.color.float32[1] = desc->clearValue.color[1]; + attachment->clearValue.color.float32[2] = desc->clearValue.color[2]; + attachment->clearValue.color.float32[3] = desc->clearValue.color[3]; + + attachment->imageView = imageView->handle; + if (resolveImageView) { + attachment->resolveImageView = resolveImageView->handle; + attachment->resolveImageLayout = layout; + } + + // load op + if (desc->loadOp == PAL_LOAD_OP_CLEAR) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + + } else if (desc->loadOp == PAL_LOAD_OP_LOAD) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + } else if (desc->loadOp == PAL_LOAD_OP_DONT_CARE) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + } + + // store op + if (desc->storeOp == PAL_STORE_OP_STORE) { + attachment->storeOp = VK_ATTACHMENT_STORE_OP_STORE; + + } else if (desc->storeOp == PAL_STORE_OP_DONT_CARE) { + attachment->storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + + attachment->resolveMode = resolveModeToVk(desc->resolveMode); + attachment->imageLayout = layout; + } + + rendering.colorAttachmentCount = info->colorAttachentCount; + rendering.pColorAttachments = colorAttachments; + + // depth attachment + if (info->depthStencilAttachment) { + attachment = &depthAttachment; + desc = info->depthStencilAttachment; + imageView = (ImageViewVk*)desc->imageView; + resolveImageView = (ImageViewVk*)desc->resolveImageView; + + attachment->sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + attachment->pNext = nullptr; + attachment->resolveImageView = nullptr; + attachment->resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + stencilAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; + stencilAttachment.pNext = nullptr; + stencilAttachment.resolveImageView = nullptr; + stencilAttachment.resolveImageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + attachment->clearValue.depthStencil.depth = desc->clearValue.depth; + stencilAttachment.clearValue.depthStencil.stencil = desc->clearValue.stencil; + + attachment->imageView = imageView->handle; + stencilAttachment.imageView = imageView->handle; + if (resolveImageView) { + attachment->resolveImageView = resolveImageView->handle; + attachment->resolveImageLayout = layout; + + stencilAttachment.resolveImageView = resolveImageView->handle; + stencilAttachment.resolveImageLayout = layout; + } + + // load op + if (desc->loadOp == PAL_LOAD_OP_CLEAR) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + + } else if (desc->loadOp == PAL_LOAD_OP_LOAD) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + } else if (desc->loadOp == PAL_LOAD_OP_DONT_CARE) { + attachment->loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + } + + // store op + if (desc->storeOp == PAL_STORE_OP_STORE) { + attachment->storeOp = VK_ATTACHMENT_STORE_OP_STORE; + + } else if (desc->storeOp == PAL_STORE_OP_DONT_CARE) { + attachment->storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + + // stencil load op + if (desc->stencilLoadOp == PAL_LOAD_OP_CLEAR) { + stencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + + } else if (desc->stencilLoadOp == PAL_LOAD_OP_LOAD) { + stencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + + } else if (desc->stencilLoadOp == PAL_LOAD_OP_DONT_CARE) { + stencilAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + } + + // stencil store op + if (desc->stencilStoreOp == PAL_STORE_OP_STORE) { + stencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + + } else if (desc->stencilStoreOp == PAL_STORE_OP_DONT_CARE) { + stencilAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + } + + attachment->resolveMode = resolveModeToVk(desc->resolveMode); + attachment->imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL; + stencilAttachment.resolveMode = resolveModeToVk(desc->resolveMode); + stencilAttachment.imageLayout = VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL; + + rendering.pDepthAttachment = &depthAttachment; + rendering.pStencilAttachment = &stencilAttachment; + } + + // fragment shading rate + if (info->fragmentShadingRateImageView) { + imageView = (ImageViewVk*)info->fragmentShadingRateImageView; + fsrInfo.imageView = imageView->handle; + fsrInfo.shadingRateAttachmentTexelSize.width = info->fragmentShadingRateTexelWidth; + fsrInfo.shadingRateAttachmentTexelSize.height = info->fragmentShadingRateTexelHeight; + fsrInfo.imageLayout = VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR; + rendering.pNext = &fsrInfo; + } + + rendering.layerCount = info->arrayLayerCount; + rendering.renderArea.offset.x = info->renderArea.x; + rendering.renderArea.offset.y = info->renderArea.y; + rendering.renderArea.extent.width = info->renderArea.width; + rendering.renderArea.extent.height = info->renderArea.height; + + if (info->viewCount == 1) { + rendering.viewMask = 0; + } else { + rendering.viewMask = (1 << info->viewCount) - 1; + } + + rendering.flags = renderingFlagToVk(info->flags); + cmdBufferImpl->device->cmdBeginRendering(cmdBufferImpl->handle, &rendering); +} + +void PAL_CALL cmdEndRenderingVk(PalCommandBuffer* cmdBuffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + cmdBufferImpl->device->cmdEndRendering(cmdBufferImpl->handle); +} + +void PAL_CALL cmdCopyBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* dstBuffer = (BufferVk*)dst; + BufferVk* srcBuffer = (BufferVk*)src; + + VkBufferCopy copyRegion = {0}; + copyRegion.size = copyInfo->size; + copyRegion.dstOffset = copyInfo->dstOffset; + copyRegion.srcOffset = copyInfo->srcOffset; + s_Vk.cmdCopyBuffer(cmdBufferImpl->handle, srcBuffer->handle, dstBuffer->handle, 1, ©Region); +} + +void PAL_CALL cmdCopyBufferToImageVk( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + ImageVk* dst = (ImageVk*)dstImage; + BufferVk* src = (BufferVk*)srcBuffer; + + VkBufferImageCopy copyRegion = {0}; + copyRegion.bufferImageHeight = copyInfo->bufferImageHeight; + copyRegion.bufferOffset = copyInfo->bufferOffset; + copyRegion.bufferRowLength = copyInfo->bufferRowLength; + + copyRegion.imageOffset.x = copyInfo->imageOffsetX; + copyRegion.imageOffset.y = copyInfo->imageOffsetY; + copyRegion.imageOffset.z = copyInfo->imageOffsetZ; + + copyRegion.imageExtent.width = copyInfo->imageWidth; + copyRegion.imageExtent.height = copyInfo->imageHeight; + copyRegion.imageExtent.depth = copyInfo->imageDepth; + + copyRegion.imageSubresource.aspectMask = imageAspectToVk(copyInfo->imageAspect); + copyRegion.imageSubresource.baseArrayLayer = copyInfo->ImageStartArrayLayer; + copyRegion.imageSubresource.layerCount = copyInfo->ImageArrayLayerCount; + copyRegion.imageSubresource.mipLevel = copyInfo->ImageMipLevel; + + s_Vk.cmdCopyBufferToImage( + cmdBufferImpl->handle, + src->handle, + dst->handle, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); +} + +void PAL_CALL cmdCopyImageVk( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + ImageVk* dstImage = (ImageVk*)dst; + ImageVk* srcImage = (ImageVk*)src; + + VkImageCopy copyRegion = {0}; + copyRegion.dstOffset.x = copyInfo->dstOffsetX; + copyRegion.dstOffset.y = copyInfo->dstOffsetY; + copyRegion.dstOffset.z = copyInfo->dstOffsetZ; + + copyRegion.srcOffset.x = copyInfo->srcOffsetX; + copyRegion.srcOffset.y = copyInfo->srcOffsetY; + copyRegion.srcOffset.z = copyInfo->srcOffsetZ; + + copyRegion.extent.width = copyInfo->width; + copyRegion.extent.height = copyInfo->height; + copyRegion.extent.depth = copyInfo->depth; + + copyRegion.dstSubresource.aspectMask = imageAspectToVk(copyInfo->aspect); + copyRegion.dstSubresource.baseArrayLayer = copyInfo->dstStartArrayLayer; + copyRegion.dstSubresource.layerCount = copyInfo->arrayLayerCount; + copyRegion.dstSubresource.mipLevel = copyInfo->dstMipLevel; + + copyRegion.srcSubresource.aspectMask = imageAspectToVk(copyInfo->aspect); + copyRegion.srcSubresource.baseArrayLayer = copyInfo->srcStartArrayLayer; + copyRegion.srcSubresource.layerCount = copyInfo->arrayLayerCount; + copyRegion.srcSubresource.mipLevel = copyInfo->srcMipLevel; + + s_Vk.cmdCopyImage( + cmdBufferImpl->handle, + srcImage->handle, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dstImage->handle, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ©Region); +} + +void PAL_CALL cmdCopyImageToBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* dst = (BufferVk*)dstBuffer; + ImageVk* src = (ImageVk*)srcImage; + + VkBufferImageCopy copyRegion = {0}; + copyRegion.bufferImageHeight = copyInfo->bufferImageHeight; + copyRegion.bufferOffset = copyInfo->bufferOffset; + copyRegion.bufferRowLength = copyInfo->bufferRowLength; + + copyRegion.imageOffset.x = copyInfo->imageOffsetX; + copyRegion.imageOffset.y = copyInfo->imageOffsetY; + copyRegion.imageOffset.z = copyInfo->imageOffsetZ; + + copyRegion.imageExtent.width = copyInfo->imageWidth; + copyRegion.imageExtent.height = copyInfo->imageHeight; + copyRegion.imageExtent.depth = copyInfo->imageDepth; + + copyRegion.imageSubresource.aspectMask = imageAspectToVk(copyInfo->imageAspect); + copyRegion.imageSubresource.baseArrayLayer = copyInfo->ImageStartArrayLayer; + copyRegion.imageSubresource.layerCount = copyInfo->ImageArrayLayerCount; + copyRegion.imageSubresource.mipLevel = copyInfo->ImageMipLevel; + + s_Vk.cmdCopyImageToBuffer( + cmdBufferImpl->handle, + src->handle, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + dst->handle, + 1, + ©Region); +} + +void PAL_CALL cmdBindPipelineVk( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipelineImpl = (PipelineVk*)pipeline; + s_Vk.cmdBindPipeline(cmdBufferImpl->handle, pipelineImpl->bindPoint, pipelineImpl->handle); + cmdBufferImpl->pipeline = pipelineImpl; +} + +void PAL_CALL cmdSetViewportVk( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkViewport* viewportsImpl = nullptr; + viewportsImpl = palLinearAlloc(&cmdBufferImpl->allocator, sizeof(VkViewport) * count, 0); + for (int i = 0; i < count; i++) { + VkViewport* tmp = &viewportsImpl[i]; + tmp->x = viewports[i].x; + tmp->y = viewports[i].y; + tmp->width = viewports[i].width; + tmp->height = viewports[i].height; + tmp->minDepth = viewports[i].minDepth; + tmp->maxDepth = viewports[i].maxDepth; + } + + s_Vk.cmdSetViewports(cmdBufferImpl->handle, 0, count, viewportsImpl); +} + +void PAL_CALL cmdSetScissorsVk( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkRect2D* scissorsImpl = nullptr; + scissorsImpl = palLinearAlloc(&cmdBufferImpl->allocator, sizeof(VkRect2D) * count, 0); + for (int i = 0; i < count; i++) { + VkRect2D* tmp = &scissorsImpl[i]; + tmp->offset.x = scissors[i].x; + tmp->offset.y = scissors[i].y; + tmp->extent.width = scissors[i].width; + tmp->extent.height = scissors[i].height; + } + + s_Vk.cmdSetScissors(cmdBufferImpl->handle, 0, count, scissorsImpl); +} + +void PAL_CALL cmdBindVertexBuffersVk( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + VkBuffer* buffersImpl = nullptr; + buffersImpl = palLinearAlloc(&cmdBufferImpl->allocator, sizeof(VkBuffer) * count, 0); + for (int i = 0; i < count; i++) { + BufferVk* tmp = (BufferVk*)buffers[i]; + buffersImpl[i] = tmp->handle; + } + + s_Vk.cmdBindVertexBuffers(cmdBufferImpl->handle, firstSlot, count, buffersImpl, offsets); +} + +void PAL_CALL cmdBindIndexBufferVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + VkIndexType bufferType = VK_INDEX_TYPE_UINT32; + if (type == PAL_INDEX_TYPE_UINT16) { + bufferType = VK_INDEX_TYPE_UINT16; + } + + s_Vk.cmdBindIndexBuffer(cmdBufferImpl->handle, bufferImpl->handle, offset, bufferType); +} + +void PAL_CALL cmdDrawVk( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + s_Vk.cmdDraw(cmdBufferImpl->handle, vertexCount, instanceCount, firstVertex, firstInstance); +} + +void PAL_CALL cmdDrawIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + uint32_t stride = sizeof(VkDrawIndirectCommand); + s_Vk.cmdDrawIndirect(cmdBufferImpl->handle, bufferImpl->handle, 0, count, stride); +} + +void PAL_CALL cmdDrawIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + BufferVk* countBufferImpl = (BufferVk*)countBuffer; + DeviceVk* device = cmdBufferImpl->device; + + uint32_t stride = sizeof(VkDrawIndirectCommand); + device->cmdDrawIndirectCount( + cmdBufferImpl->handle, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0, + maxDrawCount, + stride); +} + +void PAL_CALL cmdDrawIndexedVk( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + s_Vk.cmdDrawIndexed( + cmdBufferImpl->handle, + indexCount, + instanceCount, + firstIndex, + vertexOffset, + firstInstance); +} + +void PAL_CALL cmdDrawIndexedIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t count) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + uint32_t stride = sizeof(VkDrawIndexedIndirectCommand); + s_Vk.cmdDrawIndexedIndirect(cmdBufferImpl->handle, bufferImpl->handle, 0, count, stride); +} + +void PAL_CALL cmdDrawIndexedIndirectCountVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + BufferVk* countBufferImpl = (BufferVk*)countBuffer; + DeviceVk* device = cmdBufferImpl->device; + + uint32_t stride = sizeof(VkDrawIndexedIndirectCommand); + device->cmdDrawIndexedIndirectCount( + cmdBufferImpl->handle, + bufferImpl->handle, + 0, + countBufferImpl->handle, + 0, + maxDrawCount, + stride); +} + +void PAL_CALL cmdAccelerationStructureBarrierVk( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructure* as, + PalBarrierInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipeline = cmdBufferImpl->pipeline; + VkMemoryBarrier2KHR barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2_KHR; + + BarrierInfo old = barrierToVk(info->oldState); + BarrierInfo new = barrierToVk(info->newState); + + barrier.srcStageMask = pipelineStagesToVk(info->srcStages); + barrier.srcAccessMask = old.access; + barrier.dstStageMask = pipelineStagesToVk(info->dstStages); + barrier.dstAccessMask = new.access; + + VkDependencyInfo dependencyInfo = {0}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.memoryBarrierCount = 1; + dependencyInfo.pMemoryBarriers = &barrier; + cmdBufferImpl->device->cmdPipelineBarrier(cmdBufferImpl->handle, &dependencyInfo); +} + +void PAL_CALL cmdImageBarrierVk( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipeline = cmdBufferImpl->pipeline; + ImageVk* imageImpl = (ImageVk*)image; + VkImageMemoryBarrier2KHR barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2_KHR; + + BarrierInfo old = barrierToVk(info->oldState); + BarrierInfo new = barrierToVk(info->newState); + + barrier.srcStageMask = pipelineStagesToVk(info->srcStages); + barrier.srcAccessMask = old.access; + barrier.oldLayout = old.layout; + barrier.dstStageMask = pipelineStagesToVk(info->dstStages); + barrier.dstAccessMask = new.access; + barrier.newLayout = new.layout; + + barrier.image = imageImpl->handle; + barrier.subresourceRange.aspectMask = imageAspectToVk(subresourceRange->aspect); + barrier.subresourceRange.baseArrayLayer = subresourceRange->startArrayLayer; + barrier.subresourceRange.baseMipLevel = subresourceRange->startMipLevel; + barrier.subresourceRange.layerCount = subresourceRange->layerArrayCount; + barrier.subresourceRange.levelCount = subresourceRange->mipLevelCount; + + VkDependencyInfo dependencyInfo = {0}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.imageMemoryBarrierCount = 1; + dependencyInfo.pImageMemoryBarriers = &barrier; + cmdBufferImpl->device->cmdPipelineBarrier(cmdBufferImpl->handle, &dependencyInfo); +} + +void PAL_CALL cmdBufferBarrierVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipeline = cmdBufferImpl->pipeline; + BufferVk* bufferImpl = (BufferVk*)buffer; + VkBufferMemoryBarrier2KHR barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + + BarrierInfo old = barrierToVk(info->oldState); + BarrierInfo new = barrierToVk(info->newState); + + barrier.srcStageMask = pipelineStagesToVk(info->srcStages); + barrier.srcAccessMask = old.access; + barrier.dstStageMask = pipelineStagesToVk(info->dstStages); + barrier.dstAccessMask = new.access; + + barrier.buffer = bufferImpl->handle; + barrier.offset = 0; + barrier.size = VK_WHOLE_SIZE; + + VkDependencyInfo dependencyInfo = {0}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = 1; + dependencyInfo.pBufferMemoryBarriers = &barrier; + cmdBufferImpl->device->cmdPipelineBarrier(cmdBufferImpl->handle, &dependencyInfo); +} + +void PAL_CALL cmdDispatchVk( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + s_Vk.cmdDispatch(cmdBufferImpl->handle, groupCountX, groupCountY, groupCountZ); +} + +void PAL_CALL cmdDispatchBaseVk( + PalCommandBuffer* cmdBuffer, + uint32_t baseGroupX, + uint32_t baseGroupY, + uint32_t baseGroupZ, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + + device->cmdDispatchBase( + cmdBufferImpl->handle, + baseGroupX, + baseGroupY, + baseGroupZ, + groupCountX, + groupCountY, + groupCountZ); +} + +void PAL_CALL cmdDispatchIndirectVk( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + BufferVk* bufferImpl = (BufferVk*)buffer; + s_Vk.cmdDispatchIndirect(cmdBufferImpl->handle, bufferImpl->handle, 0); +} + +void PAL_CALL cmdTraceRaysVk( + PalCommandBuffer* cmdBuffer, + PalShaderBindingTable* sbt, + uint32_t raygenIndex, + uint32_t width, + uint32_t height, + uint32_t depth) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + ShaderBindingTableVk* sbtImpl = (ShaderBindingTableVk*)sbt; + + VkStridedDeviceAddressRegionKHR raygenAddress = {0}; + raygenAddress.size = sbtImpl->raygen.region.size; + raygenAddress.stride = sbtImpl->raygen.region.stride; + raygenAddress.deviceAddress = + sbtImpl->baseAddress + raygenIndex * sbtImpl->raygen.region.stride; + + // we need to make sure the SBT is up to date + commitShaderbindingTableUpdate(cmdBufferImpl, sbtImpl); + + cmdBufferImpl->device->cmdTraceRays( + cmdBufferImpl->handle, + &raygenAddress, + &sbtImpl->miss.region, + &sbtImpl->hit.region, + &sbtImpl->callable.region, + width, + height, + depth); +} + +void PAL_CALL cmdTraceRaysIndirectVk( + PalCommandBuffer* cmdBuffer, + uint32_t raygenIndex, + PalShaderBindingTable* sbt, + PalBuffer* buffer) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + ShaderBindingTableVk* sbtImpl = (ShaderBindingTableVk*)sbt; + BufferVk* bufferImpl = (BufferVk*)buffer; + + PalDeviceAddress address = sbtImpl->baseAddress + raygenIndex * sbtImpl->raygen.region.stride; + sbtImpl->raygen.region.deviceAddress = address; + + // we need to make sure the SBT is up to date + commitShaderbindingTableUpdate(cmdBufferImpl, sbtImpl); + + // copy user buffer data into a tmp gpu buffer abd execute with it + VkBufferCopy copyRegion = {0}; + copyRegion.size = sizeof(VkTraceRaysIndirectCommandKHR); + s_Vk.cmdCopyBuffer( + cmdBufferImpl->handle, + bufferImpl->handle, + cmdBufferImpl->buffer, + 1, + ©Region); + + // put a memory barrier + VkBufferMemoryBarrier2KHR barrier = {0}; + barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2_KHR; + barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT_KHR; + barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT_KHR; + barrier.dstStageMask = VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR; + barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT_KHR; + + barrier.buffer = cmdBufferImpl->buffer; + barrier.offset = 0; + barrier.size = VK_WHOLE_SIZE; + + VkDependencyInfo dependencyInfo = {0}; + dependencyInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dependencyInfo.bufferMemoryBarrierCount = 1; + dependencyInfo.pBufferMemoryBarriers = &barrier; + cmdBufferImpl->device->cmdPipelineBarrier(cmdBufferImpl->handle, &dependencyInfo); + + VkDeviceAddress bufAddress = 0; + VkBufferDeviceAddressInfoKHR bufferInfo = {0}; + bufferInfo.buffer = cmdBufferImpl->buffer; + bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR; + bufAddress = + cmdBufferImpl->device->getBufferrAddress(cmdBufferImpl->device->handle, &bufferInfo); + + cmdBufferImpl->device->cmdTraceRaysIndirect( + cmdBufferImpl->handle, + &sbtImpl->raygen.region, + &sbtImpl->miss.region, + &sbtImpl->hit.region, + &sbtImpl->callable.region, + bufAddress); +} + +void PAL_CALL cmdBindDescriptorSetVk( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipeline = cmdBufferImpl->pipeline; + DescriptorSetVk* setImpl = (DescriptorSetVk*)set; + + s_Vk.cmdBindDescriptorSets( + cmdBufferImpl->handle, + pipeline->bindPoint, + pipeline->layout, + setIndex, + 1, + &setImpl->handle, + 0, + nullptr); +} + +void PAL_CALL cmdPushConstantsVk( + PalCommandBuffer* cmdBuffer, + uint64_t offset, + uint64_t size, + const void* value) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + PipelineVk* pipeline = cmdBufferImpl->pipeline; + + s_Vk.cmdPushConstants( + cmdBufferImpl->handle, + pipeline->layout, + cmdBufferImpl->device->shaderStages, + offset, + size, + value); +} + +void PAL_CALL cmdSetCullModeVk( + PalCommandBuffer* cmdBuffer, + PalCullMode cullMode) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + + VkCullModeFlags cullModeImpl = 0; + switch (cullMode) { + case PAL_CULL_MODE_BACK: + cullModeImpl = VK_CULL_MODE_BACK_BIT; + + case PAL_CULL_MODE_FRONT: + cullModeImpl = VK_CULL_MODE_FRONT_BIT; + + case PAL_CULL_MODE_NONE: + cullModeImpl = VK_CULL_MODE_NONE; + } + + device->cmdSetCullMode(cmdBufferImpl->handle, cullModeImpl); +} + +void PAL_CALL cmdSetFrontFaceVk( + PalCommandBuffer* cmdBuffer, + PalFrontFace frontFace) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + + VkFrontFace frontFaceImpl = 0; + switch (frontFace) { + case PAL_FRONT_FACE_CLOCKWISE: + frontFaceImpl = VK_FRONT_FACE_CLOCKWISE; + + case PAL_FRONT_FACE_COUNTER_CLOCKWISE: + frontFaceImpl = VK_FRONT_FACE_COUNTER_CLOCKWISE; + } + + device->cmdSetFrontFace(cmdBufferImpl->handle, frontFaceImpl); +} + +void PAL_CALL cmdSetPrimitiveTopologyVk( + PalCommandBuffer* cmdBuffer, + PalPrimitiveTopology topology) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + + VkPrimitiveTopology topologyImpl = 0; + switch (topology) { + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: { + topologyImpl = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: { + topologyImpl = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_LIST: { + topologyImpl = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_STRIP: { + topologyImpl = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_POINT_LIST: { + topologyImpl = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + break; + } + } + + device->cmdSetPrimitiveTopology(cmdBufferImpl->handle, topologyImpl); +} + +void PAL_CALL cmdSetDepthTestEnableVk( + PalCommandBuffer* cmdBuffer, + PalBool enable) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + device->cmdSetDepthTestEnable(cmdBufferImpl->handle, enable); +} + +void PAL_CALL cmdSetDepthWriteEnableVk( + PalCommandBuffer* cmdBuffer, + PalBool enable) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + device->cmdSetDepthWriteEnable(cmdBufferImpl->handle, enable); +} + +void PAL_CALL cmdSetStencilOpVk( + PalCommandBuffer* cmdBuffer, + PalStencilFaceFlags faceMask, + PalStencilOp failOp, + PalStencilOp passOp, + PalStencilOp depthFailOp, + PalCompareOp compareOp) +{ + CommandBufferVk* cmdBufferImpl = (CommandBufferVk*)cmdBuffer; + DeviceVk* device = cmdBufferImpl->device; + VkStencilFaceFlags faceFlags = 0; + if (faceMask & PAL_STENCIL_FACE_FLAG_BACK) { + faceFlags |= VK_STENCIL_FACE_BACK_BIT; + } + + if (faceMask & PAL_STENCIL_FACE_FLAG_FRONT) { + faceFlags |= VK_STENCIL_FACE_FRONT_BIT; + } + + VkStencilOp failOpImpl = stencilOpToVk(failOp); + VkStencilOp passOpImpl = stencilOpToVk(passOp); + VkStencilOp depthFailOpImpl = stencilOpToVk(depthFailOp); + VkCompareOp compareOpImpl = compareOpToVk(compareOp); + + device->cmdSetStencilOp( + cmdBufferImpl->handle, + faceFlags, + failOpImpl, + passOpImpl, + depthFailOpImpl, + compareOpImpl); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_descriptors_vulkan.c b/src/graphics/vulkan/pal_descriptors_vulkan.c new file mode 100644 index 00000000..d5502105 --- /dev/null +++ b/src/graphics/vulkan/pal_descriptors_vulkan.c @@ -0,0 +1,401 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +static VkDescriptorType descriptortypeToVk(PalDescriptorType type) +{ + switch (type) { + case PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER: + return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + case PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER: + return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + + case PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE: + return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + case PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE: + return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + + case PAL_DESCRIPTOR_TYPE_SAMPLER: + return VK_DESCRIPTOR_TYPE_SAMPLER; + + case PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE: + return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + } + + return 0; +} + +PalResult PAL_CALL createDescriptorSetLayoutVk( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + VkDescriptorSetLayoutBinding* bindings = nullptr; + VkDescriptorBindingFlags* bindingFlags = nullptr; + DescriptorSetLayoutVk* layout = nullptr; + uint32_t count = info->bindingCount; + VkDescriptorSetLayoutBindingFlagsCreateInfoEXT flagsCreateInfo = {0}; + flagsCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; + + layout = palAllocate(s_Vk.allocator, sizeof(DescriptorSetLayoutVk), 0); + bindings = palAllocate(s_Vk.allocator, sizeof(VkDescriptorSetLayoutBinding) * count, 0); + if (!layout || !bindings) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (info->flags != 0) { + bindingFlags = palAllocate(s_Vk.allocator, sizeof(VkDescriptorBindingFlags) * count, 0); + if (!bindingFlags) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + VkDescriptorSetLayoutCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + createInfo.bindingCount = count; + createInfo.pBindings = bindings; + + uint32_t bindingIndex = 0; + for (int i = 0; i < count; i++) { + VkDescriptorSetLayoutBinding* binding = &bindings[i]; + binding->binding = bindingIndex++; + binding->descriptorCount = info->bindings[i].descriptorCount; + binding->descriptorType = descriptortypeToVk(info->bindings[i].descriptorType); + + binding->pImmutableSamplers = nullptr; + binding->stageFlags = deviceImpl->shaderStages; + + // set descriptor indexing flags + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND) { + bindingFlags[i] |= VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT; + bindingFlags[i] |= VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT_EXT; + } + + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND) { + bindingFlags[i] = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT_EXT; + } + } + + if (info->flags != 0) { + flagsCreateInfo.bindingCount = count; + flagsCreateInfo.pBindingFlags = bindingFlags; + createInfo.pNext = &flagsCreateInfo; + + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND) { + createInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT; + } + } + + result = s_Vk.createDescriptorSetLayout( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &layout->handle); + + palFree(s_Vk.allocator, bindings); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, layout); + return makeResultVk(result); + } + + layout->device = deviceImpl; + layout->flags = info->flags; + *outLayout = (PalDescriptorSetLayout*)layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDescriptorSetLayoutVk(PalDescriptorSetLayout* layout) +{ + DescriptorSetLayoutVk* layoutImpl = (DescriptorSetLayoutVk*)layout; + s_Vk.destroyDescriptorSetLayout( + layoutImpl->device->handle, + layoutImpl->handle, + &s_Vk.allocatorImpl); + + palFree(s_Vk.allocator, layoutImpl); +} + +PalResult PAL_CALL createDescriptorPoolVk( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + DescriptorPoolVk* pool = nullptr; + VkDescriptorPoolSize* poolSizes = nullptr; + uint32_t bindingSizeCount = info->bindingSizeCount; + VkDescriptorPoolCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + + if (info->flags & PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND) { + createInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT; + } + + pool = palAllocate(s_Vk.allocator, sizeof(DescriptorPoolVk), 0); + poolSizes = palAllocate(s_Vk.allocator, sizeof(VkDescriptorPoolSize) * bindingSizeCount, 0); + if (!pool || !poolSizes) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + for (int i = 0; i < bindingSizeCount; i++) { + VkDescriptorPoolSize* poolSize = &poolSizes[i]; + poolSize->descriptorCount = info->bindingSizes[i].bindingCount; + poolSize->type = descriptortypeToVk(info->bindingSizes[i].descriptorType); + } + + createInfo.maxSets = info->maxDescriptorSets; + createInfo.poolSizeCount = bindingSizeCount; + createInfo.pPoolSizes = poolSizes; + + result = s_Vk.createDescriptorPool( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &pool->handle); + + palFree(s_Vk.allocator, poolSizes); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, pool); + return makeResultVk(result); + } + + pool->device = deviceImpl; + pool->flags = info->flags; + *outPool = (PalDescriptorPool*)pool; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDescriptorPoolVk(PalDescriptorPool* pool) +{ + DescriptorPoolVk* poolImpl = (DescriptorPoolVk*)pool; + s_Vk.destroyDescriptorPool(poolImpl->device->handle, poolImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, pool); +} + +PalResult PAL_CALL resetDescriptorPoolVk(PalDescriptorPool* pool) +{ + DescriptorPoolVk* poolImpl = (DescriptorPoolVk*)pool; + s_Vk.resetDescriptorPool(poolImpl->device->handle, poolImpl->handle, 0); + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL allocateDescriptorSetVk( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + DescriptorPoolVk* poolImpl = (DescriptorPoolVk*)pool; + DescriptorSetLayoutVk* layoutImpl = (DescriptorSetLayoutVk*)layout; + DescriptorSetVk* set = nullptr; + + set = palAllocate(s_Vk.allocator, sizeof(DescriptorSetVk), 0); + if (!set) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkDescriptorSetAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + allocateInfo.descriptorPool = poolImpl->handle; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &layoutImpl->handle; + + result = s_Vk.allocateDescriptorSet(deviceImpl->handle, &allocateInfo, &set->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, set); + return makeResultVk(result); + } + + set->pool = poolImpl; + set->device = deviceImpl; + *outSet = (PalDescriptorSet*)set; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL updateDescriptorSetVk( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + VkWriteDescriptorSet* writes = nullptr; + VkDescriptorBufferInfo* bufferInfos = nullptr; + VkDescriptorImageInfo* imageInfos = nullptr; + VkAccelerationStructureKHR* tlas = nullptr; + VkWriteDescriptorSetAccelerationStructureKHR* tlasInfos = nullptr; + + uint32_t bufferCount = 0; + uint32_t imageCount = 0; + uint32_t tlasCount = 0; + uint32_t tlasInfoCount = 0; + + for (int i = 0; i < count; i++) { + if (infos[i].descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER || + infos[i].descriptorType == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + bufferCount += infos[i].descriptorCount; + + } else if (infos[i].descriptorType == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + tlasCount += infos[i].descriptorCount; + tlasInfoCount++; + + } else { + imageCount += infos[i].descriptorCount; + } + } + + writes = palAllocate(s_Vk.allocator, sizeof(VkWriteDescriptorSet) * count, 0); + if (!writes) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + memset(writes, 0, sizeof(VkWriteDescriptorSet) * count); + + if (bufferCount) { + bufferInfos = palAllocate(s_Vk.allocator, sizeof(VkDescriptorBufferInfo) * bufferCount, 0); + if (!bufferInfos) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(bufferInfos, 0, sizeof(VkDescriptorBufferInfo) * bufferCount); + } + + if (imageCount) { + imageInfos = palAllocate(s_Vk.allocator, sizeof(VkDescriptorImageInfo) * imageCount, 0); + if (!imageInfos) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(imageInfos, 0, sizeof(VkDescriptorImageInfo) * imageCount); + } + + if (tlasCount) { + uint32_t tlasInfoSize = + sizeof(VkWriteDescriptorSetAccelerationStructureKHR) * tlasInfoCount; + tlasInfos = palAllocate(s_Vk.allocator, tlasInfoSize, 0); + tlas = palAllocate(s_Vk.allocator, sizeof(VkAccelerationStructureKHR) * count, 0); + if (!tlasInfos || !tlas) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(tlasInfos, 0, tlasInfoSize); + memset(tlas, 0, sizeof(VkAccelerationStructureKHR) * tlasCount); + } + + // reset and reuse + bufferCount = 0; + imageCount = 0; + tlasCount = 0; + tlasInfoCount = 0; + + for (int i = 0; i < count; i++) { + VkWriteDescriptorSet* write = &writes[i]; + PalDescriptorSetWriteInfo* info = &infos[i]; + + write->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write->dstArrayElement = info->arrayElement; + write->dstBinding = info->layoutBindingIndex; + write->descriptorCount = info->descriptorCount; + write->descriptorType = descriptortypeToVk(info->descriptorType); + + DescriptorSetVk* set = (DescriptorSetVk*)info->descriptorSet; + write->dstSet = set->handle; + + for (int j = 0; j < write->descriptorCount; j++) { + if (info->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER || + info->descriptorType == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + VkDescriptorBufferInfo* bufferInfo = &bufferInfos[bufferCount + j]; + + if (info->bufferInfos) { + PalDescriptorBufferInfo* tmp = &info->bufferInfos[j]; + BufferVk* bufferImpl = (BufferVk*)tmp->buffer; + + bufferInfo->buffer = bufferImpl->handle; + bufferInfo->offset = tmp->offset; + bufferInfo->range = tmp->size; + } + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + if (info->tlasInfos) { + PalDescriptorTLASInfo* tmp = &info->tlasInfos[j]; + AccelerationStructureVk* as = (AccelerationStructureVk*)tmp->tlas; + tlas[tlasCount + j] = as->handle; + } + + } else { + VkDescriptorImageInfo* imageInfo = &imageInfos[imageCount + j]; + + if (info->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLER) { + if (info->samplerInfos) { + PalDescriptorSamplerInfo* tmp = &info->samplerInfos[j]; + SamplerVk* samplerImpl = (SamplerVk*)tmp->sampler; + imageInfo->sampler = samplerImpl->handle; + imageInfo->imageLayout = VK_IMAGE_LAYOUT_UNDEFINED; + } + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_IMAGE) { + if (info->imageViewInfos) { + PalDescriptorImageViewInfo* tmp = &info->imageViewInfos[j]; + ImageViewVk* imageViewImpl = (ImageViewVk*)tmp->imageView; + + imageInfo->sampler = nullptr; + imageInfo->imageLayout = VK_IMAGE_LAYOUT_GENERAL; + imageInfo->imageView = imageViewImpl->handle; + } + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE) { + if (info->imageViewInfos) { + PalDescriptorImageViewInfo* tmp = &info->imageViewInfos[j]; + ImageViewVk* imageViewImpl = (ImageViewVk*)tmp->imageView; + + imageInfo->sampler = nullptr; + imageInfo->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + imageInfo->imageView = imageViewImpl->handle; + } + } + } + } + + if (info->descriptorType == PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER || + info->descriptorType == PAL_DESCRIPTOR_TYPE_UNIFORM_BUFFER) { + write->pBufferInfo = &bufferInfos[bufferCount]; + bufferCount += write->descriptorCount; + + } else if (info->descriptorType == PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE) { + VkWriteDescriptorSetAccelerationStructureKHR* tlasInfo = &tlasInfos[tlasInfoCount]; + tlasInfo->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; + tlasInfo->accelerationStructureCount = info->descriptorCount; + tlasInfo->pAccelerationStructures = &tlas[tlasCount]; + + write->pNext = &tlasInfos[tlasInfoCount++]; + tlasCount += write->descriptorCount; + + } else { + write->pImageInfo = &imageInfos[imageCount]; + imageCount += write->descriptorCount; + } + } + + s_Vk.updateDescriptorSet(deviceImpl->handle, count, writes, 0, nullptr); + palFree(s_Vk.allocator, writes); + palFree(s_Vk.allocator, bufferInfos); + palFree(s_Vk.allocator, imageInfos); + palFree(s_Vk.allocator, tlasInfos); + palFree(s_Vk.allocator, tlas); + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_device_vulkan.c b/src/graphics/vulkan/pal_device_vulkan.c new file mode 100644 index 00000000..88a96fc6 --- /dev/null +++ b/src/graphics/vulkan/pal_device_vulkan.c @@ -0,0 +1,1308 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +static void fillDescriptorIndexingFeatures( + VkPhysicalDeviceDescriptorIndexingFeaturesEXT* feature, + VkPhysicalDevice phyDevice) +{ + VkPhysicalDeviceDescriptorIndexingFeaturesEXT desc = {0}; + desc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = &desc; + s_Vk.getPhysicalDeviceFeatures2(phyDevice, &features); + + // clang-format off + feature->runtimeDescriptorArray = PAL_TRUE; + feature->descriptorBindingPartiallyBound = desc.descriptorBindingPartiallyBound; + + feature->descriptorBindingUpdateUnusedWhilePending = desc.descriptorBindingUpdateUnusedWhilePending; + feature->shaderSampledImageArrayNonUniformIndexing = desc.shaderSampledImageArrayNonUniformIndexing; + feature->descriptorBindingSampledImageUpdateAfterBind = desc.descriptorBindingSampledImageUpdateAfterBind; + feature->shaderStorageImageArrayNonUniformIndexing = desc.shaderStorageImageArrayNonUniformIndexing; + feature->descriptorBindingStorageImageUpdateAfterBind = desc.descriptorBindingStorageImageUpdateAfterBind; + + feature->shaderStorageBufferArrayNonUniformIndexing = desc.shaderStorageBufferArrayNonUniformIndexing; + feature->descriptorBindingStorageBufferUpdateAfterBind = desc.descriptorBindingStorageBufferUpdateAfterBind; + feature->shaderUniformBufferArrayNonUniformIndexing = desc.shaderUniformBufferArrayNonUniformIndexing; + feature->descriptorBindingUniformBufferUpdateAfterBind = desc.descriptorBindingUniformBufferUpdateAfterBind; + // clang-format on +} + +static void loadFeatureProcs( + PalAdapterFeatures features, + DeviceVk* device) +{ + // clang-format off + // swapchain procs + if (features & PAL_ADAPTER_FEATURE_SWAPCHAIN) { + device->acquireNextImage = (PFN_vkAcquireNextImageKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkAcquireNextImageKHR"); + + device->createSwapchain = (PFN_vkCreateSwapchainKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCreateSwapchainKHR"); + + device->destroySwapchain = (PFN_vkDestroySwapchainKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkDestroySwapchainKHR"); + + device->getSwapchainImages = (PFN_vkGetSwapchainImagesKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetSwapchainImagesKHR"); + + device->queuePresent = (PFN_vkQueuePresentKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkQueuePresentKHR"); + } + + // semaphore procs + if (features & PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE) { + device->waitSemaphore = (PFN_vkWaitSemaphores)s_Vk.getDeviceProcAddr( + device->handle, + "vkWaitSemaphores"); + + device->signalSemaphore = (PFN_vkSignalSemaphore)s_Vk.getDeviceProcAddr( + device->handle, + "vkSignalSemaphore"); + + device->getSemaphoreValue = (PFN_vkGetSemaphoreCounterValue)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetSemaphoreCounterValue"); + + if (!device->waitSemaphore) { + device->waitSemaphore = (PFN_vkWaitSemaphoresKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkWaitSemaphoresKHR"); + + device->signalSemaphore = (PFN_vkSignalSemaphoreKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkSignalSemaphoreKHR"); + + device->getSemaphoreValue = (PFN_vkGetSemaphoreCounterValueKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetSemaphoreCounterValueKHR"); + } + } + + // fragment shading rate procs + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) { + device->cmdSetFragmentShadingRate = + (PFN_vkCmdSetFragmentShadingRateKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdSetFragmentShadingRateKHR"); + } + + // mesh shader procs + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + device->cmdDrawMeshTask = (PFN_vkCmdDrawMeshTasksEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawMeshTasksEXT"); + + device->cmdDrawMeshTaskIndirect = + (PFN_vkCmdDrawMeshTasksIndirectEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawMeshTasksIndirectEXT"); + + device->cmdDrawMeshTaskIndirectCount = + (PFN_vkCmdDrawMeshTasksIndirectCountEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawMeshTasksIndirectCountEXT"); + } + + // ray tracing procs + device->limits.maxPayloadSize = 0; + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + device->limits.maxPayloadSize = 64; + + device->createAccelerationStructure = + (PFN_vkCreateAccelerationStructureKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCreateAccelerationStructureKHR"); + + device->destroyAccelerationStructure = + (PFN_vkDestroyAccelerationStructureKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkDestroyAccelerationStructureKHR"); + + device->getAccelerationBuildsize = + (PFN_vkGetAccelerationStructureBuildSizesKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetAccelerationStructureBuildSizesKHR"); + + device->cmdBuildAccelerationStructures = + (PFN_vkCmdBuildAccelerationStructuresKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdBuildAccelerationStructuresKHR"); + + device->getAccelerationDeviceAddress = + (PFN_vkGetAccelerationStructureDeviceAddressKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetAccelerationStructureDeviceAddressKHR"); + + device->cmdTraceRays = + (PFN_vkCmdTraceRaysKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdTraceRaysKHR"); + + device->createRayTracingPipeline = + (PFN_vkCreateRayTracingPipelinesKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCreateRayTracingPipelinesKHR"); + + device->cmdTraceRaysIndirect = + (PFN_vkCmdTraceRaysIndirectKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdTraceRaysIndirectKHR"); + + device->getRayTracingShaderGroupHandles = + (PFN_vkGetRayTracingShaderGroupHandlesKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetRayTracingShaderGroupHandlesKHR"); + } + + // buffer address procs + if (features & PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS) { + device->getBufferrAddress = + (PFN_vkGetBufferDeviceAddress)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetBufferDeviceAddress"); + + if (!device->getBufferrAddress) { + device->getBufferrAddress = + (PFN_vkGetBufferDeviceAddressKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkGetBufferDeviceAddressKHR"); + } + } + + // indirect draw count procs + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT) { + device->cmdDrawIndirectCount = + (PFN_vkCmdDrawIndirectCount)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawIndirectCount"); + + device->cmdDrawIndexedIndirectCount = + (PFN_vkCmdDrawIndexedIndirectCount)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawIndexedIndirectCount"); + + if (!device->cmdDrawIndirectCount) { + device->cmdDrawIndirectCount = + (PFN_vkCmdDrawIndirectCountKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawIndirectCountKHR"); + + device->cmdDrawIndexedIndirectCount = + (PFN_vkCmdDrawIndexedIndirectCountKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDrawIndexedIndirectCountKHR"); + } + } + + // dispatch base procs + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) { + device->cmdDispatchBase = + (PFN_vkCmdDispatchBase)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDispatchBase"); + + if (!device->cmdDispatchBase) { + device->cmdDispatchBase = + (PFN_vkCmdDispatchBaseKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdDispatchBaseKHR"); + } + } + + // dynamic rendering procs + device->cmdBeginRendering = + (PFN_vkCmdBeginRendering)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdBeginRendering"); + + device->cmdEndRendering = + (PFN_vkCmdEndRendering)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdEndRendering"); + + device->cmdPipelineBarrier = + (PFN_vkCmdPipelineBarrier2)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdPipelineBarrier2"); + + device->queueSubmit = + (PFN_vkQueueSubmit2)s_Vk.getDeviceProcAddr( + device->handle, + "vkQueueSubmit2"); + + if (!device->cmdBeginRendering) { + device->cmdBeginRendering = + (PFN_vkCmdBeginRenderingKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdBeginRenderingKHR"); + + device->cmdEndRendering = + (PFN_vkCmdEndRenderingKHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdEndRenderingKHR"); + + device->cmdPipelineBarrier = + (PFN_vkCmdPipelineBarrier2KHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdPipelineBarrier2KHR"); + + device->queueSubmit = + (PFN_vkQueueSubmit2KHR)s_Vk.getDeviceProcAddr( + device->handle, + "vkQueueSubmit2KHR"); + } + + // dynamic states + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE) { + device->cmdSetCullMode = + (PFN_vkCmdSetCullMode)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdSetCullMode"); + + if (!device->cmdSetCullMode) { + device->cmdSetCullMode = + (PFN_vkCmdSetCullModeEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkCmdSetCullModeEXT"); + } + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE) { + device->cmdSetFrontFace = + (PFN_vkCmdSetFrontFace)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetFrontFace"); + + if (!device->cmdSetFrontFace) { + device->cmdSetFrontFace = + (PFN_vkCmdSetFrontFaceEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetFrontFaceEXT"); + } + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY) { + device->cmdSetPrimitiveTopology = + (PFN_vkCmdSetPrimitiveTopology)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetPrimitiveTopology"); + + if (!device->cmdSetPrimitiveTopology) { + device->cmdSetPrimitiveTopology = + (PFN_vkCmdSetPrimitiveTopologyEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetPrimitiveTopologyEXT"); + } + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE) { + device->cmdSetDepthTestEnable = + (PFN_vkCmdSetDepthTestEnable)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetDepthTestEnable"); + + if (!device->cmdSetDepthTestEnable) { + device->cmdSetDepthTestEnable = + (PFN_vkCmdSetDepthTestEnableEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetDepthTestEnableEXT"); + } + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE) { + device->cmdSetDepthWriteEnable = + (PFN_vkCmdSetDepthWriteEnable)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetDepthWriteEnable"); + + if (!device->cmdSetDepthWriteEnable) { + device->cmdSetDepthWriteEnable = + (PFN_vkCmdSetDepthWriteEnableEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetDepthWriteEnableEXT"); + } + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP) { + device->cmdSetStencilOp = + (PFN_vkCmdSetStencilOp)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetStencilOp"); + + if (!device->cmdSetStencilOp) { + device->cmdSetStencilOp = + (PFN_vkCmdSetStencilOpEXT)s_Vk.getDeviceProcAddr( + device->handle, + "vkcmdSetStencilOpEXT"); + } + } + + // clang-format on +} + +static VkShaderStageFlags shaderStageToVK(PalShaderStage stage) +{ + switch (stage) { + case PAL_SHADER_STAGE_VERTEX: + return VK_SHADER_STAGE_VERTEX_BIT; + + case PAL_SHADER_STAGE_FRAGMENT: + return VK_SHADER_STAGE_FRAGMENT_BIT; + + case PAL_SHADER_STAGE_COMPUTE: + return VK_SHADER_STAGE_COMPUTE_BIT; + + case PAL_SHADER_STAGE_GEOMETRY: + return VK_SHADER_STAGE_GEOMETRY_BIT; + + case PAL_SHADER_STAGE_MESH: + return VK_SHADER_STAGE_MESH_BIT_EXT; + + case PAL_SHADER_STAGE_TASK: + return VK_SHADER_STAGE_TASK_BIT_EXT; + + case PAL_SHADER_STAGE_TESSELLATION_CONTROL: + return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + + case PAL_SHADER_STAGE_TESSELLATION_EVALUATION: + return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + + case PAL_SHADER_STAGE_RAYGEN: + return VK_SHADER_STAGE_RAYGEN_BIT_KHR; + + case PAL_SHADER_STAGE_CLOSEST_HIT: + return VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + + case PAL_SHADER_STAGE_ANY_HIT: + return VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + + case PAL_SHADER_STAGE_MISS: + return VK_SHADER_STAGE_MISS_BIT_KHR; + + case PAL_SHADER_STAGE_INTERSECTION: + return VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + + case PAL_SHADER_STAGE_CALLABLE: + return VK_SHADER_STAGE_CALLABLE_BIT_KHR; + } + + return 0; +} + +PalResult PAL_CALL createDeviceVk( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice) +{ + float priority = 1.0f; + uint32_t phyQueueCount = 0; + uint32_t queueFamilyCount = 0; + VkResult result = VK_SUCCESS; + DeviceVk* device = nullptr; + VkPhysicalDeviceProperties props = {0}; + + AdapterVk* adapterImpl = (AdapterVk*)adapter; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)adapterImpl->handle; + s_Vk.getPhysicalDeviceProperties(phyDevice, &props); + + VkQueueFamilyProperties* queueFamilyProps = nullptr; + VkDeviceQueueCreateInfo* queueCreateInfos = nullptr; + s_Vk.getPhysicalDeviceQueueFamilyProperties(phyDevice, &queueFamilyCount, nullptr); + + uint32_t queueFamilySize = sizeof(VkQueueFamilyProperties) * queueFamilyCount; + uint32_t queueCreateInfosSize = sizeof(VkDeviceQueueCreateInfo) * queueFamilyCount; + + queueFamilyProps = palAllocate(s_Vk.allocator, queueFamilySize, 0); + queueCreateInfos = palAllocate(s_Vk.allocator, queueCreateInfosSize, 0); + device = palAllocate(s_Vk.allocator, sizeof(DeviceVk), 0); + if (!queueFamilyProps || !queueCreateInfos || !device) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(device, 0, sizeof(DeviceVk)); + device->phyDevice = phyDevice; + s_Vk.getPhysicalDeviceQueueFamilyProperties(phyDevice, &queueFamilyCount, queueFamilyProps); + for (int i = 0; i < queueFamilyCount; i++) { + queueCreateInfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queueCreateInfos[i].pNext = nullptr; + queueCreateInfos[i].pQueuePriorities = &priority; + queueCreateInfos[i].queueFamilyIndex = i; + queueCreateInfos[i].queueCount = queueFamilyProps[i].queueCount; + queueCreateInfos[i].flags = 0; + + // we need the total number of physical queues + phyQueueCount += queueFamilyProps[i].queueCount; + } + + device->phyQueues = palAllocate(s_Vk.allocator, sizeof(PhysicalQueue) * phyQueueCount, 0); + if (!device->phyQueues) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // build features and extensions capabilities + VkPhysicalDeviceFeatures phyDeviceFeatures = {0}; + s_Vk.getPhysicalDeviceFeatures(phyDevice, &phyDeviceFeatures); + + VkPhysicalDeviceFeatures coreFeatures = {0}; + if (features & PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY) { + coreFeatures.samplerAnisotropy = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING) { + coreFeatures.sampleRateShading = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEWPORT) { + coreFeatures.multiViewport = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_TESSELLATION_SHADER) { + coreFeatures.tessellationShader = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_GEOMETRY_SHADER) { + coreFeatures.geometryShader = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT16) { + coreFeatures.shaderInt16 = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT64) { + coreFeatures.shaderInt64 = PAL_TRUE; + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT64) { + coreFeatures.shaderFloat64 = PAL_TRUE; + } + + // extensions and features2 + void* next = nullptr; + int extCount = 0; + const char* extensions[32] = {0}; + + VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline = {0}; + timeline.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR; + + VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader16 = {0}; + shader16.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR; + + VkPhysicalDeviceMeshShaderFeaturesEXT mesh = {0}; + mesh.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT; + + VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray = {0}; + VkPhysicalDeviceAccelerationStructureFeaturesKHR acc = {0}; + ray.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + acc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + + VkPhysicalDeviceFragmentShadingRateFeaturesKHR fsr = {0}; + fsr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR; + + VkPhysicalDeviceDescriptorIndexingFeatures descIndex = {0}; + descIndex.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES; + + VkPhysicalDeviceMultiviewFeatures multiView = {0}; + multiView.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; + + VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamicState = {0}; + dynamicState.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; + + VkPhysicalDeviceExtendedDynamicState2FeaturesEXT dynamicState2 = {0}; + dynamicState2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_2_FEATURES_EXT; + + VkPhysicalDeviceBufferDeviceAddressFeaturesKHR bufferAddress = {0}; + bufferAddress.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_KHR; + + VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRendering = {0}; + dynamicRendering.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + + VkPhysicalDeviceSynchronization2FeaturesKHR sync2 = {0}; + sync2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR; + + VkPhysicalDeviceShaderDrawParametersFeatures drawParameters = {0}; + drawParameters.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_DRAW_PARAMETERS_FEATURES; + + VkPhysicalDeviceRobustness2FeaturesEXT nullDescriptors = {0}; + nullDescriptors.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT; + + if (props.apiVersion < VK_API_VERSION_1_3) { + extensions[extCount++] = "VK_KHR_dynamic_rendering"; + extensions[extCount++] = "VK_KHR_synchronization2"; + } + + dynamicRendering.dynamicRendering = PAL_TRUE; + sync2.synchronization2 = PAL_TRUE; + + dynamicRendering.pNext = next; + sync2.pNext = &dynamicRendering; + next = &sync2; + + if (features & PAL_ADAPTER_FEATURE_SWAPCHAIN) { + extensions[extCount++] = "VK_KHR_swapchain"; + } + + if (features & PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE) { + extensions[extCount++] = "VK_KHR_depth_stencil_resolve"; + } + + if (features & PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_timeline_semaphore"; + } + timeline.timelineSemaphore = PAL_TRUE; + + timeline.pNext = next; + next = &timeline; + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT16) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_shader_float16_int8"; + } + shader16.shaderFloat16 = PAL_TRUE; + + shader16.pNext = next; + next = &shader16; + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + extensions[extCount++] = "VK_KHR_ray_tracing_pipeline"; + extensions[extCount++] = "VK_KHR_acceleration_structure"; + extensions[extCount++] = "VK_KHR_deferred_host_operations"; + ray.rayTracingPipeline = PAL_TRUE; + acc.accelerationStructure = PAL_TRUE; + + ray.pNext = next; + acc.pNext = &ray; + next = &acc; + } + + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + extensions[extCount++] = "VK_EXT_mesh_shader"; + mesh.meshShader = PAL_TRUE; + mesh.taskShader = PAL_TRUE; + + // mesh shader needs geometry feature for primitives + coreFeatures.geometryShader = PAL_TRUE; + + // msh draw indirect count + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT) { + extensions[extCount++] = "VK_KHR_draw_mesh_tasks_indirect_count"; + } + + mesh.pNext = next; + next = &mesh; + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_draw_indirect_count"; + } + } + + if ((features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) || + (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT)) { + extensions[extCount++] = "VK_KHR_fragment_shading_rate"; + fsr.pipelineFragmentShadingRate = PAL_TRUE; + + // fragment shading rate needs this + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT) { + fsr.attachmentFragmentShadingRate = PAL_TRUE; + } + + fsr.pNext = next; + next = &fsr; + } + + if (features & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_EXT_descriptor_indexing"; + } + + fillDescriptorIndexingFeatures(&descIndex, phyDevice); + descIndex.pNext = next; + next = &descIndex; + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEW) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_multiview"; + } + multiView.multiview = PAL_TRUE; + + multiView.pNext = next; + next = &multiView; + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE || + features & PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE || + features & PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY) { + if (props.apiVersion < VK_API_VERSION_1_3) { + extensions[extCount++] = "VK_EXT_extended_dynamic_state"; + } + dynamicState.extendedDynamicState = PAL_TRUE; + + dynamicState.pNext = next; + next = &dynamicState; + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE || + features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE || + features & PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP) { + extensions[extCount++] = "VK_EXT_extended_dynamic_state2"; + dynamicState2.extendedDynamicState2 = PAL_TRUE; + + dynamicState2.pNext = next; + next = &dynamicState2; + } + + if (features & PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_buffer_device_address"; + } + bufferAddress.bufferDeviceAddress = PAL_TRUE; + + bufferAddress.pNext = next; + next = &bufferAddress; + } + + if (features & PAL_ADAPTER_FEATURE_DISPATCH_BASE) { + if (props.apiVersion < VK_API_VERSION_1_2) { + extensions[extCount++] = "VK_KHR_shader_draw_parameters"; + } + drawParameters.shaderDrawParameters = PAL_TRUE; + + drawParameters.pNext = next; + next = &drawParameters; + } + + if (features & PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS) { + extensions[extCount++] = "VK_EXT_robustness2"; + nullDescriptors.nullDescriptor = PAL_TRUE; + + nullDescriptors.pNext = next; + next = &nullDescriptors; + } + + VkDeviceCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + createInfo.pEnabledFeatures = &coreFeatures; + createInfo.enabledExtensionCount = extCount; + createInfo.ppEnabledExtensionNames = extensions; + createInfo.pQueueCreateInfos = queueCreateInfos; + createInfo.queueCreateInfoCount = queueFamilyCount; + createInfo.pNext = next; + + result = s_Vk.createDevice(phyDevice, &createInfo, &s_Vk.allocatorImpl, &device->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, queueFamilyProps); + palFree(s_Vk.allocator, queueCreateInfos); + palFree(s_Vk.allocator, device->phyQueues); + palFree(s_Vk.allocator, device); + return makeResultVk(result); + } + + device->phyQueueIndex = 0; + device->queueFamilyCount = queueFamilyCount; + device->phyQueueCount = phyQueueCount; + + // get queues + for (int i = 0; i < queueFamilyCount; i++) { + VkQueueFamilyProperties* data = &queueFamilyProps[i]; + for (int j = 0; j < data->queueCount; j++) { + PhysicalQueue* queue = &device->phyQueues[i]; + s_Vk.getDeviceQueue(device->handle, i, j, &queue->handle); + queue->usages = data->queueFlags; + queue->usedUsages = 0; + queue->familyIndex = i; + queue->phyDevice = phyDevice; + } + } + + // cache memory type indices + VkPhysicalDeviceMemoryProperties memProps = {0}; + s_Vk.getPhysicalDeviceMemoryProperties(phyDevice, &memProps); + + memset(device->memoryClassMask, 0, sizeof(uint32_t) * 3); + for (int i = 0; i < memProps.memoryTypeCount; i++) { + VkMemoryPropertyFlags flags = memProps.memoryTypes[i].propertyFlags; + + uint32_t bit = (1u << i); + if ((flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) && + !(flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) { + device->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY] |= bit; + } + + if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) { + device->memoryClassMask[PAL_MEMORY_TYPE_CPU_UPLOAD] |= bit; + } + + if ((flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) && + (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT)) { + device->memoryClassMask[PAL_MEMORY_TYPE_CPU_READBACK] |= bit; + } + } + + // HACK: most CPU drivers dont have a vram so we set the vram to system memory + if (device->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY] == 0) { + device->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY] = + device->memoryClassMask[PAL_MEMORY_TYPE_CPU_UPLOAD]; + } + + // cache shader stages + device->shaderStages |= VK_SHADER_STAGE_VERTEX_BIT; + device->shaderStages |= VK_SHADER_STAGE_FRAGMENT_BIT; + device->shaderStages |= VK_SHADER_STAGE_COMPUTE_BIT; + + if (features & PAL_ADAPTER_FEATURE_GEOMETRY_SHADER) { + device->shaderStages |= VK_SHADER_STAGE_GEOMETRY_BIT; + } + + if (features & PAL_ADAPTER_FEATURE_TESSELLATION_SHADER) { + device->shaderStages |= VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT; + device->shaderStages |= VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; + } + + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + device->shaderStages |= VK_SHADER_STAGE_TASK_BIT_EXT; + device->shaderStages |= VK_SHADER_STAGE_MESH_BIT_EXT; + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + device->shaderStages |= VK_SHADER_STAGE_RAYGEN_BIT_KHR; + device->shaderStages |= VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + device->shaderStages |= VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + device->shaderStages |= VK_SHADER_STAGE_MISS_BIT_KHR; + device->shaderStages |= VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + device->shaderStages |= VK_SHADER_STAGE_CALLABLE_BIT_KHR; + } + + loadFeatureProcs(features, device); + palFree(s_Vk.allocator, queueFamilyProps); + palFree(s_Vk.allocator, queueCreateInfos); + + device->features = features; + *outDevice = (PalDevice*)device; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyDeviceVk(PalDevice* device) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + s_Vk.destroyDevice(deviceImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, deviceImpl->phyQueues); + palFree(s_Vk.allocator, deviceImpl); +} + +uint32_t PAL_CALL getDeviceLostReasonVk(PalDevice* device) +{ + return (uint32_t)VK_ERROR_DEVICE_LOST; +} + +PalResult PAL_CALL allocateMemoryVk( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory) +{ + VkResult result; + MemoryVk* memory = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + VkMemoryAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = size; + VkMemoryAllocateFlagsInfo allocateFlagsInfo = {0}; + + memory = palAllocate(s_Vk.allocator, sizeof(MemoryVk), 0); + if (!memory) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + uint32_t memoryTypeMask = 0; + uint32_t usages = 0; + palUnpackUint32(memoryMask, &memoryTypeMask, &usages); + uint32_t memoryClassMask = deviceImpl->memoryClassMask[type] & memoryTypeMask; + + // pick an index using the scoring system and check if the memory index is valid + uint32_t memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, memoryClassMask); + if (!(memoryClassMask & (1u << memoryIndex))) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + allocateInfo.memoryTypeIndex = memoryIndex; + if (usages & PAL_BUFFER_USAGE_DEVICE_ADDRESS) { + allocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + allocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + allocateInfo.pNext = &allocateFlagsInfo; + } + + result = s_Vk.allocateMemory( + deviceImpl->handle, + &allocateInfo, + &s_Vk.allocatorImpl, + &memory->handle); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + memory->device = deviceImpl; + memory->type = type; + *outMemory = (PalMemory*)memory; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL freeMemoryVk(PalMemory* memory) +{ + MemoryVk* memoryImpl = (MemoryVk*)memory; + s_Vk.freeMemory(memoryImpl->device->handle, memoryImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, memoryImpl); +} + +void PAL_CALL querySamplerAnisotropyCapabilitiesVk( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties props = {0}; + s_Vk.getPhysicalDeviceProperties(deviceImpl->phyDevice, &props); + caps->maxAnisotropy = props.limits.maxSamplerAnisotropy; +} + +void PAL_CALL queryMultiViewCapabilitiesVk( + PalDevice* device, + PalMultiViewCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceMultiviewPropertiesKHR props = {0}; + VkPhysicalDeviceProperties2 properties2 = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES_KHR; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + caps->maxViewCount = props.maxMultiviewViewCount; + if (caps->maxViewCount == 0) { + caps->maxViewCount = 1; + } +} + +void PAL_CALL queryMultiViewportCapabilitiesVk( + PalDevice* device, + PalMultiViewportCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties props = {0}; + s_Vk.getPhysicalDeviceProperties(deviceImpl->phyDevice, &props); + caps->maxCount = props.limits.maxViewports; +} + +void PAL_CALL queryDepthStencilCapabilitiesVk( + PalDevice* device, + PalDepthStencilCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + VkPhysicalDeviceDepthStencilResolvePropertiesKHR props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES_KHR; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + caps->supportsIndependentResolve = props.independentResolve; + caps->supportsIndependentResolveNone = props.independentResolveNone; + + // depth + if (props.supportedDepthResolveModes & VK_RESOLVE_MODE_AVERAGE_BIT_KHR) { + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_AVERAGE); + } + + if (props.supportedDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR) { + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_SAMPLE_ZERO); + } + + if (props.supportedDepthResolveModes & VK_RESOLVE_MODE_MIN_BIT_KHR) { + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_MIN); + } + + if (props.supportedDepthResolveModes & VK_RESOLVE_MODE_MAX_BIT_KHR) { + caps->supportedDepthResolveModes |= (1u << PAL_RESOLVE_MODE_MAX); + } + + // stencil + if (props.supportedStencilResolveModes & VK_RESOLVE_MODE_AVERAGE_BIT_KHR) { + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_AVERAGE); + } + + if (props.supportedStencilResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT_KHR) { + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_SAMPLE_ZERO); + } + + if (props.supportedStencilResolveModes & VK_RESOLVE_MODE_MIN_BIT_KHR) { + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_MIN); + } + + if (props.supportedStencilResolveModes & VK_RESOLVE_MODE_MAX_BIT_KHR) { + caps->supportedStencilResolveModes |= (1u << PAL_RESOLVE_MODE_MAX); + } +} + +void PAL_CALL queryFragmentShadingRateCapabilitiesVk( + PalDevice* device, + PalFragmentShadingRateCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + VkPhysicalDeviceFragmentShadingRatePropertiesKHR props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + memset(caps, 0, sizeof(PalFragmentShadingRateCapabilities)); + for (int i = 0; i < PAL_FRAGMENT_SHADING_RATE_COUNT; i++) { + VkExtent2D size = getShadingRateSizeVk((PalFragmentShadingRate)i); + + // check against the max size + if (size.width <= props.maxFragmentSize.width || + size.height <= props.maxFragmentSize.height) { + caps->supportedShadingRates |= (1u << i); + } + } + + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE); + if (props.fragmentShadingRateNonTrivialCombinerOps) { + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX); + caps->supportedShadingRates |= (1u << PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL); + } + + VkExtent2D size = props.minFragmentShadingRateAttachmentTexelSize; + caps->minTexelWidth = size.width; + caps->minTexelHeight = size.height; + size = props.maxFragmentShadingRateAttachmentTexelSize; + caps->maxTexelWidth = size.width; + caps->maxTexelHeight = size.height; +} + +void PAL_CALL queryMeshShaderCapabilitiesVk( + PalDevice* device, + PalMeshShaderCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + VkPhysicalDeviceMeshShaderPropertiesEXT props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + caps->maxOutputPrimitives = props.maxMeshOutputPrimitives; + caps->maxOutputVertices = props.maxMeshOutputVertices; + caps->maxTaskWorkGroupInvocations = props.maxTaskWorkGroupInvocations; + caps->maxWorkGroupInvocations = props.maxMeshWorkGroupInvocations; + + caps->maxTaskWorkGroupCount[0] = props.maxTaskWorkGroupCount[0]; + caps->maxTaskWorkGroupCount[1] = props.maxTaskWorkGroupCount[1]; + caps->maxTaskWorkGroupCount[2] = props.maxTaskWorkGroupCount[2]; + + caps->maxWorkGroupCount[0] = props.maxMeshWorkGroupCount[0]; + caps->maxWorkGroupCount[1] = props.maxMeshWorkGroupCount[1]; + caps->maxWorkGroupCount[2] = props.maxMeshWorkGroupCount[2]; +} + +void PAL_CALL queryRayTracingCapabilitiesVk( + PalDevice* device, + PalRayTracingCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + VkPhysicalDeviceRayTracingPipelinePropertiesKHR props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR; + VkPhysicalDeviceAccelerationStructurePropertiesKHR accProps = {0}; + accProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; + props.pNext = &accProps; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + caps->maxRecursionDepth = props.maxRayRecursionDepth; + caps->maxHitAttributeSize = props.maxRayHitAttributeSize; + caps->maxInstanceCount = accProps.maxInstanceCount; + caps->maxPrimitiveCount = accProps.maxPrimitiveCount; + caps->maxGeometryCount = accProps.maxGeometryCount; + + caps->maxPayloadSize = 64; // safe + caps->maxDispatchInvocations = props.maxRayDispatchInvocationCount; +} + +void PAL_CALL queryDescriptorIndexingCapabilitiesVk( + PalDevice* device, + PalDescriptorIndexingCapabilities* caps) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDeviceDescriptorIndexingFeaturesEXT desc = {0}; + desc.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + + VkPhysicalDeviceProperties2 properties2 = {0}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + + VkPhysicalDeviceDescriptorIndexingPropertiesEXT props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_PROPERTIES_EXT; + + VkPhysicalDeviceFeatures2 features; + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + + VkPhysicalDeviceAccelerationStructurePropertiesKHR accProps = {0}; + accProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_PROPERTIES_KHR; + + features.pNext = &desc; + props.pNext = &accProps; + properties2.pNext = &props; + s_Vk.getPhysicalDeviceFeatures2(deviceImpl->phyDevice, &features); + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &properties2); + + caps->flags = 0; + if (desc.descriptorBindingPartiallyBound) { + caps->flags |= PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND; + } + + // clang-format off + if (desc.descriptorBindingUpdateUnusedWhilePending && + desc.descriptorBindingSampledImageUpdateAfterBind && + desc.descriptorBindingStorageImageUpdateAfterBind && + desc.descriptorBindingStorageBufferUpdateAfterBind && + desc.descriptorBindingUniformBufferUpdateAfterBind) { + caps->flags |= PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND; + } + + if (desc.shaderSampledImageArrayNonUniformIndexing && + desc.shaderStorageImageArrayNonUniformIndexing && + desc.shaderStorageBufferArrayNonUniformIndexing && + desc.shaderUniformBufferArrayNonUniformIndexing) { + caps->flags |= PAL_DESCRIPTOR_INDEXING_FLAG_NON_UNIFORM_INDEXING; + } + // clang-format on + + caps->maxPerStageSampledImages = props.maxPerStageDescriptorUpdateAfterBindSampledImages; + caps->maxPerSetSampledImages = props.maxDescriptorSetUpdateAfterBindSampledImages; + caps->maxPerStageStorageImages = props.maxPerStageDescriptorUpdateAfterBindStorageImages; + caps->maxPerSetStorageImages = props.maxDescriptorSetUpdateAfterBindStorageImages; + + caps->maxPerStageSamplers = props.maxPerStageDescriptorUpdateAfterBindSamplers; + caps->maxPerSetSamplers = props.maxDescriptorSetUpdateAfterBindSamplers; + caps->maxPerStageStorageBuffers = props.maxPerStageDescriptorUpdateAfterBindStorageBuffers; + caps->maxPerSetStorageBuffers = props.maxDescriptorSetUpdateAfterBindStorageBuffers; + + caps->maxPerStageUniformBuffers = props.maxPerStageDescriptorUpdateAfterBindUniformBuffers; + caps->maxPerSetUniformBuffers = props.maxDescriptorSetUpdateAfterBindUniformBuffers; + + uint32_t tmp = accProps.maxPerStageDescriptorUpdateAfterBindAccelerationStructures; + uint32_t tmp2 = accProps.maxDescriptorSetUpdateAfterBindAccelerationStructures; + caps->maxPerStageAccelerationStructure = tmp; + caps->maxPerSetAccelerationStructure = tmp2; +} + +PalResult PAL_CALL createQueueVk( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + VkQueueFlags queueFlag = 0; + QueueVk* queue = nullptr; + + if (deviceImpl->phyQueueCount == 0) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + switch (type) { + case PAL_QUEUE_TYPE_COMPUTE: { + queueFlag = VK_QUEUE_COMPUTE_BIT; + break; + } + + case PAL_QUEUE_TYPE_GRAPHICS: { + queueFlag = VK_QUEUE_GRAPHICS_BIT; + break; + } + + case PAL_QUEUE_TYPE_COPY: { + queueFlag = VK_QUEUE_TRANSFER_BIT; + break; + } + } + + // we index the for loop so we dont always start at the beginning, this way + // we cycle through all queue families each time we create a queue + PhysicalQueue* phyQueue = nullptr; + for (int i = deviceImpl->phyQueueIndex; i < deviceImpl->phyQueueCount; i++) { + PhysicalQueue* pq = &deviceImpl->phyQueues[i]; + // check if the physical queue supports the requested operation + // and if its not already used + if (pq->usages & queueFlag && pq->usedUsages != queueFlag) { + pq->usedUsages |= queueFlag; + phyQueue = pq; + break; + } + } + + if (!phyQueue) { + // we didnt get any queue, we check if we started the loop at the beginning or mid way + if (deviceImpl->phyQueueIndex == 0) { + // we searched all queue families + return PAL_RESULT_CODE_OUT_OF_MEMORY; + + } else { + // we start at the beginning and go through the queue families again + deviceImpl->phyQueueIndex = 0; + for (int i = deviceImpl->phyQueueIndex; i < deviceImpl->phyQueueCount; i++) { + PhysicalQueue* pq = &deviceImpl->phyQueues[i]; + if (pq->usages & queueFlag && pq->usedUsages != queueFlag) { + pq->usedUsages |= queueFlag; + phyQueue = pq; + break; + } + } + + if (!phyQueue) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + } + + queue = palAllocate(s_Vk.allocator, sizeof(QueueVk), 0); + if (!queue) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + deviceImpl->phyQueueIndex = (deviceImpl->phyQueueIndex + 1) % deviceImpl->phyQueueCount; + queue->phyQueue = phyQueue; + queue->usage = queueFlag; + queue->device = deviceImpl; + *outQueue = (PalQueue*)queue; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyQueueVk(PalQueue* queue) +{ + QueueVk* queueImpl = (QueueVk*)queue; + PhysicalQueue* phyQueue = queueImpl->phyQueue; + phyQueue->usedUsages &= ~queueImpl->usage; + palFree(s_Vk.allocator, queueImpl); +} + +PalResult PAL_CALL waitQueueVk(PalQueue* queue) +{ + QueueVk* queueImpl = (QueueVk*)queue; + VkResult result = s_Vk.waitQueue(queueImpl->phyQueue->handle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalBool PAL_CALL canQueuePresentVk( + PalQueue* queue, + PalSurface* surface) +{ + VkResult result; + QueueVk* queueImpl = (QueueVk*)queue; + PhysicalQueue* phyQueue = queueImpl->phyQueue; + SurfaceVk* surfaceImpl = (SurfaceVk*)surface; + + // check if the queue is a graphics queue before we check its family + // index for presentation support. + if (queueImpl->usage != VK_QUEUE_GRAPHICS_BIT) { + return PAL_FALSE; + } + + VkBool32 supported = PAL_FALSE; + result = s_Vk.checkSurfaceSupport( + phyQueue->phyDevice, + phyQueue->familyIndex, + surfaceImpl->handle, + &supported); + + if (result == VK_SUCCESS && supported) { + return PAL_TRUE; + } + + return PAL_FALSE; +} + +PalResult PAL_CALL createShaderVk( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader) +{ + VkResult result; + ShaderVk* shader = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + + shader = palAllocate(s_Vk.allocator, sizeof(ShaderVk), 0); + if (!shader) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // allocate entries array + shader->entries = palAllocate(s_Vk.allocator, sizeof(ShaderEntry) * info->entryCount, 0); + if (!shader->entries) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + for (int i = 0; i < info->entryCount; i++) { + ShaderEntry* entry = &shader->entries[i]; + strncpy(entry->entryName, info->entries[i].entryName, PAL_SHADER_ENTRY_NAME_SIZE); + entry->entryName[PAL_SHADER_ENTRY_NAME_SIZE - 1] = '\0'; + entry->patchControlPoints = info->entries[i].patchControlPoints; + entry->stage = shaderStageToVK(info->entries[i].stage); + } + + VkShaderModuleCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + createInfo.codeSize = info->codeSize; + createInfo.pCode = (const uint32_t*)info->code; + + result = + s_Vk.createShader(deviceImpl->handle, &createInfo, &s_Vk.allocatorImpl, &shader->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, shader); + return makeResultVk(result); + } + + shader->device = deviceImpl; + shader->entryCount = info->entryCount; + *outShader = (PalShader*)shader; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyShaderVk(PalShader* shader) +{ + ShaderVk* shaderImpl = (ShaderVk*)shader; + s_Vk.destroyShader(shaderImpl->device->handle, shaderImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, shaderImpl->entries); + palFree(s_Vk.allocator, shaderImpl); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_image_vulkan.c b/src/graphics/vulkan/pal_image_vulkan.c new file mode 100644 index 00000000..2a281682 --- /dev/null +++ b/src/graphics/vulkan/pal_image_vulkan.c @@ -0,0 +1,429 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +static VkImageUsageFlags imageUsageToVk(PalImageUsages usages) +{ + VkImageUsageFlags flags = 0; + if (usages & PAL_IMAGE_USAGE_COLOR_ATTACHEMENT) { + flags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + } + + if (usages & PAL_IMAGE_USAGE_DEPTH_ATTACHEMENT) { + flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + } + + if (usages & PAL_IMAGE_USAGE_TRANSFER_SRC) { + flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + } + + if (usages & PAL_IMAGE_USAGE_TRANSFER_DST) { + flags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + } + + if (usages & PAL_IMAGE_USAGE_STORAGE) { + flags |= VK_IMAGE_USAGE_STORAGE_BIT; + } + + if (usages & PAL_IMAGE_USAGE_SAMPLED) { + flags |= VK_IMAGE_USAGE_SAMPLED_BIT; + } + + return flags; +} + +static VkImageViewType imageViewTypeToVk(PalImageViewType type) +{ + switch (type) { + case PAL_IMAGE_VIEW_TYPE_1D: + return VK_IMAGE_VIEW_TYPE_1D; + + case PAL_IMAGE_VIEW_TYPE_1D_ARRAY: + return VK_IMAGE_VIEW_TYPE_1D_ARRAY; + + case PAL_IMAGE_VIEW_TYPE_2D: + return VK_IMAGE_VIEW_TYPE_2D; + + case PAL_IMAGE_VIEW_TYPE_2D_ARRAY: + return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + + case PAL_IMAGE_VIEW_TYPE_3D: + return VK_IMAGE_VIEW_TYPE_3D; + + case PAL_IMAGE_VIEW_TYPE_CUBE: + return VK_IMAGE_VIEW_TYPE_CUBE; + + case PAL_IMAGE_VIEW_TYPE_CUBE_ARRAY: + return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + } + + return VK_IMAGE_VIEW_TYPE_2D; +} + +static VkFilter filterToVk(PalFilterMode mode) +{ + switch (mode) { + case PAL_FILTER_MODE_NEAREST: { + return VK_FILTER_NEAREST; + } + + case PAL_FILTER_MODE_LINEAR: { + return VK_FILTER_LINEAR; + } + } + return VK_FILTER_NEAREST; +} + +static VkSamplerMipmapMode mipmapModeToVk(PalSamplerMipmapMode mode) +{ + switch (mode) { + case PAL_SAMPLER_MIPMAP_MODE_NEAREST: { + return VK_SAMPLER_MIPMAP_MODE_NEAREST; + } + + case PAL_SAMPLER_MIPMAP_MODE_LINEAR: { + return VK_SAMPLER_MIPMAP_MODE_LINEAR; + } + } + return VK_SAMPLER_MIPMAP_MODE_NEAREST; +} + +static VkSamplerAddressMode addressModeToVk(PalSamplerAddressMode mode) +{ + switch (mode) { + case PAL_SAMPLER_ADDRESS_MODE_REPEAT: { + return VK_SAMPLER_ADDRESS_MODE_REPEAT; + } + + case PAL_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: { + return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + } + case PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: { + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + } + case PAL_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: { + return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + } + } + return VK_SAMPLER_ADDRESS_MODE_REPEAT; +} + +static VkBorderColor borderColorToVk(PalBorderColor color) +{ + switch (color) { + case PAL_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK: { + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + } + + case PAL_BORDER_COLOR_INT_TRANSPARENT_BLACK: { + return VK_BORDER_COLOR_INT_TRANSPARENT_BLACK; + } + + case PAL_BORDER_COLOR_FLOAT_OPAQUE_BLACK: { + return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + } + + case PAL_BORDER_COLOR_INT_OPAQUE_BLACK: { + return VK_BORDER_COLOR_INT_OPAQUE_BLACK; + } + + case PAL_BORDER_COLOR_FLOAT_OPAQUE_WHITE: { + return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } + + case PAL_BORDER_COLOR_INT_OPAQUE_WHITE: { + return VK_BORDER_COLOR_INT_OPAQUE_WHITE; + } + } + return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; +} + +PalResult PAL_CALL createImageVk( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage) +{ + VkResult result; + ImageVk* image = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + MemoryVk* memory = nullptr; + + image = palAllocate(s_Vk.allocator, sizeof(ImageVk), 0); + if (!image) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (info->memoryUsage != PAL_IMAGE_MEMORY_USAGE_MANUAL) { + memory = palAllocate(s_Vk.allocator, sizeof(MemoryVk), 0); + if (!memory) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + VkImageCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.tiling = VK_IMAGE_TILING_OPTIMAL; + createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + createInfo.extent.width = info->width; + createInfo.extent.height = info->height; + createInfo.extent.depth = info->depth; + + createInfo.arrayLayers = info->arrayLayerCount; + createInfo.mipLevels = info->mipLevelCount; + createInfo.format = formatToVk(info->format); + createInfo.samples = samplesToVk(info->sampleCount); + createInfo.usage = imageUsageToVk(info->usages); + + createInfo.imageType = VK_IMAGE_TYPE_2D; + if (info->type == PAL_IMAGE_TYPE_3D) { + createInfo.imageType = VK_IMAGE_TYPE_3D; + + } else if (info->type == PAL_IMAGE_TYPE_1D) { + createInfo.imageType = VK_IMAGE_TYPE_1D; + } + + result = s_Vk.createImage(deviceImpl->handle, &createInfo, &s_Vk.allocatorImpl, &image->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, image); + return makeResultVk(result); + } + + image->isMemoryManaged = PAL_FALSE; + if (info->memoryUsage != PAL_IMAGE_MEMORY_USAGE_MANUAL) { + PalMemoryType memoryType = PAL_MEMORY_TYPE_GPU_ONLY; + + // allocate and manage memory + VkMemoryRequirements memReq = {0}; + s_Vk.getImageMemoryRequirements(deviceImpl->handle, image->handle, &memReq); + + VkMemoryAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = (VkDeviceSize)memReq.size; + + uint32_t memoryMask = deviceImpl->memoryClassMask[memoryType] & memReq.memoryTypeBits; + uint32_t memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, memoryMask); + if (!(memoryMask & (1u << memoryIndex))) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + allocateInfo.memoryTypeIndex = memoryIndex; + result = s_Vk.allocateMemory( + deviceImpl->handle, + &allocateInfo, + &s_Vk.allocatorImpl, + &memory->handle); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + result = s_Vk.bindImageMemory(deviceImpl->handle, image->handle, memory->handle, 0); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + memory->type = memoryType; + image->isMemoryManaged = PAL_TRUE; + } + + image->device = deviceImpl; + image->info.type = info->type; + image->info.format = info->format; + image->info.usages = info->usages; + image->info.height = info->height; + image->info.mipLevelCount = info->mipLevelCount; + image->info.sampleCount = info->sampleCount; + image->info.width = info->width; + image->info.depth = info->depth; + image->info.arrayLayerCount = info->arrayLayerCount; + image->info.belongsToSwapchain = PAL_FALSE; + + image->memory = memory; + *outImage = (PalImage*)image; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyImageVk(PalImage* image) +{ + ImageVk* imageImpl = (ImageVk*)image; + if (imageImpl->info.belongsToSwapchain) { + return; + } + + s_Vk.destroyImage(imageImpl->device->handle, imageImpl->handle, &s_Vk.allocatorImpl); + if (imageImpl->isMemoryManaged) { + s_Vk.freeMemory(imageImpl->device->handle, imageImpl->memory->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, imageImpl->memory); + } + + palFree(s_Vk.allocator, imageImpl); +} + +void PAL_CALL getImageInfoVk( + PalImage* image, + PalImageInfo* info) +{ + ImageVk* imageImpl = (ImageVk*)image; + *info = imageImpl->info; +} + +void PAL_CALL getImageMemoryRequirementsVk( + PalImage* image, + PalMemoryRequirements* requirements) +{ + ImageVk* imageImpl = (ImageVk*)image; + if (imageImpl->info.belongsToSwapchain) { + return; + } + + DeviceVk* device = imageImpl->device; + VkMemoryRequirements memReq = {0}; + s_Vk.getImageMemoryRequirements(device->handle, imageImpl->handle, &memReq); + requirements->alignment = (uint64_t)memReq.alignment; + requirements->size = (uint64_t)memReq.size; + requirements->memoryMask = palPackUint32(memReq.memoryTypeBits, 0); + requirements->supportedMemoryTypes = 0; + + if ((memReq.memoryTypeBits & device->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY]) != 0) { + requirements->supportedMemoryTypes |= (1u << PAL_MEMORY_TYPE_GPU_ONLY); + } +} + +PalResult PAL_CALL bindImageMemoryVk( + PalImage* image, + PalMemory* memory, + uint64_t offset) +{ + ImageVk* imageImpl = (ImageVk*)image; + if (imageImpl->info.belongsToSwapchain) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + if (imageImpl->memory) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + MemoryVk* memoryImpl = (MemoryVk*)memory; + s_Vk.bindImageMemory(imageImpl->device->handle, imageImpl->handle, memoryImpl->handle, offset); + imageImpl->memory = memoryImpl; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createImageViewVk( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView) +{ + VkResult result = VK_SUCCESS; + ImageViewVk* imageView = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + ImageVk* imageImpl = (ImageVk*)image; + + imageView = palAllocate(s_Vk.allocator, sizeof(ImageViewVk), 0); + if (!imageView) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkImageViewCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.format = formatToVk(info->format); + createInfo.image = imageImpl->handle; + createInfo.viewType = imageViewTypeToVk(info->type); + + createInfo.subresourceRange.aspectMask = imageAspectToVk(info->subresourceRange.aspect); + createInfo.subresourceRange.baseArrayLayer = info->subresourceRange.startArrayLayer; + createInfo.subresourceRange.baseMipLevel = info->subresourceRange.startMipLevel; + createInfo.subresourceRange.levelCount = info->subresourceRange.mipLevelCount; + createInfo.subresourceRange.layerCount = info->subresourceRange.layerArrayCount; + + result = s_Vk.createImageView( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &imageView->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, imageView); + return makeResultVk(result); + } + + imageView->device = deviceImpl; + imageView->image = imageImpl; + imageView->layerCount = createInfo.subresourceRange.layerCount; + *outImageView = (PalImageView*)imageView; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyImageViewVk(PalImageView* imageView) +{ + ImageViewVk* imageViewImpl = (ImageViewVk*)imageView; + s_Vk.destroyImageView( + imageViewImpl->device->handle, + imageViewImpl->handle, + &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, imageViewImpl); +} + +PalResult PAL_CALL createSamplerVk( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler) +{ + VkResult result = VK_SUCCESS; + SamplerVk* sampler = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + + sampler = palAllocate(s_Vk.allocator, sizeof(SamplerVk), 0); + if (!sampler) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkSamplerCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + createInfo.anisotropyEnable = info->enableAnisotropy; + createInfo.compareEnable = info->enableCompare; + + createInfo.mipLodBias = info->mipLodBias; + createInfo.minLod = info->minLod; + createInfo.maxLod = info->maxLod; + createInfo.maxAnisotropy = info->maxAnisotropy; + createInfo.compareOp = compareOpToVk(info->compareOp); + + createInfo.minFilter = filterToVk(info->minFilterMode); + createInfo.magFilter = filterToVk(info->magFilterMode); + createInfo.mipmapMode = mipmapModeToVk(info->mipmapMode); + + createInfo.addressModeU = addressModeToVk(info->addressModeU); + createInfo.addressModeV = addressModeToVk(info->addressModeV); + createInfo.addressModeW = addressModeToVk(info->addressModeW); + createInfo.borderColor = borderColorToVk(info->borderColor); + + result = + s_Vk.createSampler(deviceImpl->handle, &createInfo, &s_Vk.allocatorImpl, &sampler->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, sampler); + return makeResultVk(result); + } + + sampler->device = deviceImpl; + *outSampler = (PalSampler*)sampler; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySamplerVk(PalSampler* sampler) +{ + SamplerVk* samplerImpl = (SamplerVk*)sampler; + s_Vk.destroySampler(samplerImpl->device->handle, samplerImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, samplerImpl); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_pipeline_vulkan.c b/src/graphics/vulkan/pal_pipeline_vulkan.c new file mode 100644 index 00000000..e237216b --- /dev/null +++ b/src/graphics/vulkan/pal_pipeline_vulkan.c @@ -0,0 +1,883 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +#define max(a, b) (a > b) ? a : b + +static VkBlendOp blendOpToVk(PalBlendOp op) +{ + switch (op) { + case PAL_BLEND_OP_ADD: + return VK_BLEND_OP_ADD; + + case PAL_BLEND_OP_SUBTRACT: + return VK_BLEND_OP_SUBTRACT; + + case PAL_BLEND_OP_REVERSE_SUBTRACT: + return VK_BLEND_OP_REVERSE_SUBTRACT; + + case PAL_BLEND_OP_MIN: + return VK_BLEND_OP_MIN; + + case PAL_BLEND_OP_MAX: + return VK_BLEND_OP_MAX; + } + + return VK_BLEND_OP_ADD; +} + +static VkBlendFactor blendFactorToVk(PalBlendFactor op) +{ + switch (op) { + case PAL_BLEND_FACTOR_ZERO: + return VK_BLEND_FACTOR_ZERO; + + case PAL_BLEND_FACTOR_ONE: + return VK_BLEND_FACTOR_ONE; + + case PAL_BLEND_FACTOR_SRC_COLOR: + return VK_BLEND_FACTOR_SRC_COLOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_SRC_COLOR: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + + case PAL_BLEND_FACTOR_DST_COLOR: + return VK_BLEND_FACTOR_DST_COLOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_DST_COLOR: + return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + + case PAL_BLEND_FACTOR_SRC_ALPHA: + return VK_BLEND_FACTOR_SRC_ALPHA; + + case PAL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: + return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + + case PAL_BLEND_FACTOR_DST_ALPHA: + return VK_BLEND_FACTOR_DST_ALPHA; + + case PAL_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: + return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + + case PAL_BLEND_FACTOR_CONSTANT_COLOR: + return VK_BLEND_FACTOR_CONSTANT_COLOR; + + case PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR: + return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; + + case PAL_BLEND_FACTOR_CONSTANT_ALPHA: + return VK_BLEND_FACTOR_CONSTANT_ALPHA; + + case PAL_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA: + return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA; + } + + return VK_BLEND_FACTOR_ZERO; +} + +static uint32_t getVertexTypeSizeVk(PalVertexType type) +{ + // count x sizeof type returned as size + switch (type) { + case PAL_VERTEX_TYPE_INT8_2: + case PAL_VERTEX_TYPE_UINT8_2: + case PAL_VERTEX_TYPE_INT8_2NORM: + case PAL_VERTEX_TYPE_UINT8_2NORM: { + return 2; + } + + case PAL_VERTEX_TYPE_INT32: + case PAL_VERTEX_TYPE_UINT32: + case PAL_VERTEX_TYPE_INT8_4: + case PAL_VERTEX_TYPE_INT8_4NORM: + case PAL_VERTEX_TYPE_UINT8_4: + case PAL_VERTEX_TYPE_UINT8_4NORM: + case PAL_VERTEX_TYPE_INT16_2NORM: + case PAL_VERTEX_TYPE_INT16_2: + case PAL_VERTEX_TYPE_UINT16_2: + case PAL_VERTEX_TYPE_UINT16_2NORM: + case PAL_VERTEX_TYPE_FLOAT: + case PAL_VERTEX_TYPE_HALF_FLOAT16_2: { + return 4; + } + + case PAL_VERTEX_TYPE_INT32_2: + case PAL_VERTEX_TYPE_UINT32_2: + case PAL_VERTEX_TYPE_INT16_4: + case PAL_VERTEX_TYPE_UINT16_4: + case PAL_VERTEX_TYPE_UINT16_4NORM: + case PAL_VERTEX_TYPE_INT16_4NORM: + case PAL_VERTEX_TYPE_FLOAT2: + case PAL_VERTEX_TYPE_HALF_FLOAT16_4: { + return 8; + } + + case PAL_VERTEX_TYPE_INT32_3: + case PAL_VERTEX_TYPE_UINT32_3: + case PAL_VERTEX_TYPE_FLOAT3: { + return 12; + } + + case PAL_VERTEX_TYPE_INT32_4: + case PAL_VERTEX_TYPE_UINT32_4: + case PAL_VERTEX_TYPE_FLOAT4: { + return 16; + } + } + + return 0; +} + +PalResult PAL_CALL createPipelineLayoutVk( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + PipelineLayoutVk* layout = nullptr; + VkPushConstantRange pushConstantRange = {0}; + VkDescriptorSetLayout* descriptorLayouts = nullptr; + + layout = palAllocate(s_Vk.allocator, sizeof(PipelineLayoutVk), 0); + if (!layout) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + if (info->descriptorSetLayoutCount) { + descriptorLayouts = palAllocate( + s_Vk.allocator, + sizeof(VkDescriptorSetLayout) * info->descriptorSetLayoutCount, + 0); + + if (!descriptorLayouts) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + } + + for (int i = 0; i < info->descriptorSetLayoutCount; i++) { + DescriptorSetLayoutVk* tmp = (DescriptorSetLayoutVk*)info->descriptorSetLayouts[i]; + descriptorLayouts[i] = tmp->handle; + } + + VkPipelineLayoutCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + createInfo.setLayoutCount = info->descriptorSetLayoutCount; + createInfo.pSetLayouts = descriptorLayouts; + + if (info->usePushConstant) { + pushConstantRange.size = info->pushConstantInfo.size; + pushConstantRange.stageFlags = deviceImpl->shaderStages; + pushConstantRange.offset = info->pushConstantInfo.offset; + createInfo.pushConstantRangeCount = 1; + createInfo.pPushConstantRanges = &pushConstantRange; + } + + result = s_Vk.createPipelineLayout( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &layout->handle); + + if (info->descriptorSetLayoutCount) { + palFree(s_Vk.allocator, descriptorLayouts); + } + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, layout); + return makeResultVk(result); + } + + layout->device = deviceImpl; + *outLayout = (PalPipelineLayout*)layout; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyPipelineLayoutVk(PalPipelineLayout* layout) +{ + PipelineLayoutVk* pipelineLayout = (PipelineLayoutVk*)layout; + s_Vk.destroyPipelineLayout( + pipelineLayout->device->handle, + pipelineLayout->handle, + &s_Vk.allocatorImpl); + + palFree(s_Vk.allocator, layout); +} + +PalResult PAL_CALL createGraphicsPipelineVk( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + VkResult result; + PipelineVk* pipeline = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + PipelineLayoutVk* layout = (PipelineLayoutVk*)info->pipelineLayout; + + VkPipelineShaderStageCreateInfo* shaderStages = nullptr; + VkDynamicState dynamicStates[16]; + VkVertexInputBindingDescription* bindingDescs = nullptr; + VkVertexInputAttributeDescription* attribDescs = nullptr; + VkPipelineColorBlendAttachmentState* blendattachments = nullptr; + + VkPipelineVertexInputStateCreateInfo vertexInputState = {0}; + vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {0}; + inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + + VkPipelineDynamicStateCreateInfo dynamicState = {0}; + dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + + VkPipelineRasterizationStateCreateInfo rasterizerState = {0}; + rasterizerState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + + VkPipelineMultisampleStateCreateInfo multisampleState = {0}; + multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + + VkPipelineDepthStencilStateCreateInfo depthStencilState = {0}; + depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + + VkPipelineColorBlendStateCreateInfo colorBlendState = {0}; + colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + + VkPipelineTessellationStateCreateInfo tessellationState = {0}; + tessellationState.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + + VkPipelineViewportStateCreateInfo viewportState = {0}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + + VkPipelineFragmentShadingRateStateCreateInfoKHR fsrState = {0}; + fsrState.sType = VK_STRUCTURE_TYPE_PIPELINE_FRAGMENT_SHADING_RATE_STATE_CREATE_INFO_KHR; + + VkPipelineRenderingCreateInfoKHR dynRendering = {0}; + dynRendering.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; + + VkGraphicsPipelineCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + createInfo.renderPass = VK_NULL_HANDLE; + createInfo.layout = layout->handle; + + // find the total number of shader stages + uint32_t stageCount = 0; + for (int i = 0; i < info->shaderCount; i++) { + ShaderVk* shader = (ShaderVk*)info->shaders[i]; + stageCount += shader->entryCount; + } + + pipeline = palAllocate(s_Vk.allocator, sizeof(PipelineVk), 0); + shaderStages = + palAllocate(s_Vk.allocator, sizeof(VkPipelineShaderStageCreateInfo) * stageCount, 0); + + if (!pipeline || !shaderStages) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // shaders + uint32_t stageIndex = 0; + memset(shaderStages, 0, sizeof(VkPipelineShaderStageCreateInfo) * stageCount); + for (int i = 0; i < info->shaderCount; i++) { + ShaderVk* tmp = (ShaderVk*)info->shaders[i]; + + for (int j = 0; j < tmp->entryCount; j++) { + ShaderEntry* entry = &tmp->entries[j]; + VkPipelineShaderStageCreateInfo* stageInfo = &shaderStages[stageIndex++]; + + if (entry->patchControlPoints) { + tessellationState.patchControlPoints = entry->patchControlPoints; + createInfo.pTessellationState = &tessellationState; + + if (info->topology != PAL_PRIMITIVE_TOPOLOGY_PATCH) { + palFree(s_Vk.allocator, pipeline); + return PAL_RESULT_CODE_INVALID_OPERATION; + } + } + + stageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo->module = tmp->handle; + stageInfo->pName = entry->entryName; + stageInfo->stage = entry->stage; + } + } + + createInfo.stageCount = stageCount; + createInfo.pStages = shaderStages; + + // Vertex input state + // get the max size of vertex attributes in all layouts + uint32_t vertexCount = 0; + for (int i = 0; i < info->vertexLayoutCount; i++) { + PalVertexLayout* layout = &info->vertexLayouts[i]; + vertexCount += layout->attributeCount; + } + + if (vertexCount) { + uint32_t tmpBindingSize = sizeof(VkVertexInputBindingDescription) * info->vertexLayoutCount; + uint32_t tmpAttribSize = sizeof(VkVertexInputAttributeDescription) * vertexCount; + bindingDescs = palAllocate(s_Vk.allocator, tmpBindingSize, 0); + attribDescs = palAllocate(s_Vk.allocator, tmpAttribSize, 0); + if (!bindingDescs || !attribDescs) { + palFree(s_Vk.allocator, pipeline); + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + uint32_t location = 0; + for (int i = 0; i < info->vertexLayoutCount; i++) { + PalVertexLayout* layout = &info->vertexLayouts[i]; + VkVertexInputBindingDescription* bindingDesc = &bindingDescs[i]; + + bindingDesc->binding = layout->binding; + if (layout->type == PAL_VERTEX_LAYOUT_TYPE_PER_INSTANCE) { + bindingDesc->inputRate = VK_VERTEX_INPUT_RATE_INSTANCE; + } else { + bindingDesc->inputRate = VK_VERTEX_INPUT_RATE_VERTEX; + } + + // find the stride and offset of the layout + bindingDesc->stride = 0; + uint32_t offset = 0; + for (int j = 0; j < layout->attributeCount; j++) { + PalVertexAttribute* vertexAttrib = &layout->attributes[j]; + VkVertexInputAttributeDescription* attribDesc = &attribDescs[j]; + + attribDesc->format = vertexTypeToVk(vertexAttrib->type); + attribDesc->binding = bindingDesc->binding; + attribDesc->location = location++; + + // build offsets and stride + uint32_t size = getVertexTypeSizeVk(vertexAttrib->type); + attribDesc->offset = offset; + offset += size; + bindingDesc->stride += size; + } + } + + vertexInputState.pVertexAttributeDescriptions = attribDescs; + vertexInputState.vertexAttributeDescriptionCount = vertexCount; + vertexInputState.pVertexBindingDescriptions = bindingDescs; + vertexInputState.vertexBindingDescriptionCount = info->vertexLayoutCount; + } + createInfo.pVertexInputState = &vertexInputState; + + // Input assembly + VkPrimitiveTopology topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + switch (info->topology) { + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: { + topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: { + topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_LIST: { + topology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_LINE_STRIP: { + topology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + break; + } + + case PAL_PRIMITIVE_TOPOLOGY_POINT_LIST: { + topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + break; + } + } + inputAssemblyState.topology = topology; + inputAssemblyState.primitiveRestartEnable = info->primitiveRestartEnable; + createInfo.pInputAssemblyState = &inputAssemblyState; + + // Dynamic states + uint32_t dynCount = 0; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_VIEWPORT; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_SCISSOR; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_LINE_WIDTH; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_BLEND_CONSTANTS; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_DEPTH_BIAS; + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE; + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_CULL_MODE_EXT; + } + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_FRONT_FACE_EXT; + } + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT; + } + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT; + } + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT; + } + + if (deviceImpl->features & PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_STENCIL_OP_EXT; + } + + if (info->fragmentShadingRateState) { + dynamicStates[dynCount++] = VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR; + } + + dynamicState.dynamicStateCount = dynCount; + dynamicState.pDynamicStates = dynamicStates; + createInfo.pDynamicState = &dynamicState; + + // Rasterizer state + rasterizerState.frontFace = VK_FRONT_FACE_CLOCKWISE; + if (info->rasterizerState) { + PalRasterizerState* state = info->rasterizerState; + if (state->cullMode == PAL_CULL_MODE_NONE) { + rasterizerState.cullMode = VK_CULL_MODE_NONE; + + } else if (state->cullMode == PAL_CULL_MODE_BACK) { + rasterizerState.cullMode = VK_CULL_MODE_BACK_BIT; + + } else if (state->cullMode == PAL_CULL_MODE_FRONT) { + rasterizerState.cullMode = VK_CULL_MODE_FRONT_BIT; + } + + if (state->polygonMode == PAL_POLYGON_MODE_FILL) { + rasterizerState.polygonMode = VK_POLYGON_MODE_FILL; + + } else { + rasterizerState.polygonMode = VK_POLYGON_MODE_LINE; + } + + if (state->frontFace == PAL_FRONT_FACE_CLOCKWISE) { + rasterizerState.frontFace = VK_FRONT_FACE_CLOCKWISE; + + } else { + rasterizerState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; + } + + rasterizerState.depthBiasEnable = state->enableDepthBias; + rasterizerState.depthClampEnable = state->enableDepthClamp; + rasterizerState.depthBiasConstantFactor = state->depthBiasConstant; + rasterizerState.depthBiasSlopeFactor = state->depthBiasSlope; + rasterizerState.depthBiasClamp = state->depthBiasClamp; + } + + rasterizerState.lineWidth = 1.0f; + createInfo.pRasterizationState = &rasterizerState; + + // Multisample state + uint32_t sampleMask[2] = {0}; // PAL supports upto 64 samples + multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + multisampleState.pSampleMask = nullptr; + if (info->multisampleState) { + PalMultisampleState* state = info->multisampleState; + multisampleState.alphaToCoverageEnable = state->enableAlphaToCoverage; + multisampleState.minSampleShading = state->minSampleShading; + multisampleState.sampleShadingEnable = state->enableSampleShading; + multisampleState.rasterizationSamples = samplesToVk(state->sampleCount); + + // clang-format off + if (state->sampleMask) { + if (state->sampleCount == PAL_SAMPLE_COUNT_1 || + state->sampleCount == PAL_SAMPLE_COUNT_2 || + state->sampleCount == PAL_SAMPLE_COUNT_4 || + state->sampleCount == PAL_SAMPLE_COUNT_8 || + state->sampleCount == PAL_SAMPLE_COUNT_16 || + state->sampleCount == PAL_SAMPLE_COUNT_32) { + sampleMask[0] = (uint32_t)(state->sampleMask & 0xFFFFFFFFULL); + + } else { + sampleMask[0] = (uint32_t)(state->sampleMask & 0xFFFFFFFFULL); + sampleMask[1] = (uint32_t)((state->sampleMask >> 32) & 0xFFFFFFFFULL); + } + multisampleState.pSampleMask = sampleMask; + + } else { + multisampleState.pSampleMask = nullptr; + } + // clang-format on + } + createInfo.pMultisampleState = &multisampleState; + + // Depth stencil state + if (info->depthStencilState) { + PalDepthStencilState* state = info->depthStencilState; + PalStencilOpState* back = &state->backStencilOpState; + PalStencilOpState* front = &state->frontStencilOpState; + + VkStencilOpState* backImpl = &depthStencilState.back; + VkStencilOpState* frontImpl = &depthStencilState.front; + + backImpl->compareOp = compareOpToVk(back->compareOp); + backImpl->depthFailOp = stencilOpToVk(back->depthFailOp); + backImpl->failOp = stencilOpToVk(back->failOp); + backImpl->passOp = stencilOpToVk(back->passOp); + + frontImpl->compareOp = compareOpToVk(front->compareOp); + frontImpl->depthFailOp = stencilOpToVk(front->depthFailOp); + frontImpl->failOp = stencilOpToVk(front->failOp); + frontImpl->passOp = stencilOpToVk(front->passOp); + + depthStencilState.depthCompareOp = compareOpToVk(state->compareOp); + depthStencilState.depthTestEnable = state->enableDepthTest; + depthStencilState.depthWriteEnable = state->enableDepthWrite; + depthStencilState.stencilTestEnable = state->enableStencilTest; + } + createInfo.pDepthStencilState = &depthStencilState; + + // Color blend state + if (info->colorBlendAttachmentCount) { + uint32_t count = info->colorBlendAttachmentCount; + uint32_t size = sizeof(VkPipelineColorBlendAttachmentState) * count; + blendattachments = palAllocate(s_Vk.allocator, size, 0); + if (!blendattachments) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + for (int i = 0; i < count; i++) { + VkPipelineColorBlendAttachmentState* tmp = &blendattachments[i]; + PalColorBlendAttachment* desc = &info->colorBlendAttachments[i]; + + tmp->blendEnable = desc->enableBlend; + tmp->alphaBlendOp = blendOpToVk(desc->alphaBlendOp); + tmp->colorBlendOp = blendOpToVk(desc->colorBlendOp); + + tmp->srcAlphaBlendFactor = blendFactorToVk(desc->srcAlphaBlendFactor); + tmp->srcColorBlendFactor = blendFactorToVk(desc->srcColorBlendFactor); + + tmp->dstAlphaBlendFactor = blendFactorToVk(desc->dstAlphaBlendFactor); + tmp->dstColorBlendFactor = blendFactorToVk(desc->dstColorBlendFactor); + + // blend color write mask + tmp->colorWriteMask = 0; + if (desc->colorWriteMask & PAL_COLOR_MASK_RED) { + tmp->colorWriteMask |= VK_COLOR_COMPONENT_R_BIT; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_GREEN) { + tmp->colorWriteMask |= VK_COLOR_COMPONENT_G_BIT; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_BLUE) { + tmp->colorWriteMask |= VK_COLOR_COMPONENT_B_BIT; + } + + if (desc->colorWriteMask & PAL_COLOR_MASK_ALPHA) { + tmp->colorWriteMask |= VK_COLOR_COMPONENT_A_BIT; + } + } + + colorBlendState.attachmentCount = count; + colorBlendState.pAttachments = blendattachments; + } + createInfo.pColorBlendState = &colorBlendState; + + // viewport state + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + createInfo.pViewportState = &viewportState; + + // Fragment shading rate + if (info->fragmentShadingRateState) { + PalFragmentShadingRateState* state = info->fragmentShadingRateState; + for (int i = 0; i < 2; i++) { + VkFragmentShadingRateCombinerOpKHR combinerOp; + combinerOp = combinerOpsToVk(state->combinerOps[i]); + fsrState.combinerOps[i] = combinerOp; + } + + VkExtent2D size = getShadingRateSizeVk(state->rate); + fsrState.fragmentSize = size; + createInfo.pNext = &fsrState; + } + + // layout info + VkFormat format = VK_FORMAT_UNDEFINED; + VkFormat* colorAttachments = nullptr; + PalRenderingLayoutInfo* renderingLayout = info->renderingLayout; + + colorAttachments = + palAllocate(s_Vk.allocator, sizeof(VkFormat) * renderingLayout->colorAttachentCount, 0); + + if (!colorAttachments) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // color attachments + for (int i = 0; i < renderingLayout->colorAttachentCount; i++) { + format = formatToVk(renderingLayout->colorAttachmentsFormat[i]); + colorAttachments[i] = format; + } + dynRendering.colorAttachmentCount = renderingLayout->colorAttachentCount; + dynRendering.pColorAttachmentFormats = colorAttachments; + + // depth stencil attachment + format = formatToVk(renderingLayout->depthStencilAttachmentFormat); + dynRendering.depthAttachmentFormat = format; + dynRendering.stencilAttachmentFormat = format; + + if (renderingLayout->viewCount == 1) { + dynRendering.viewMask = 0; + } else { + dynRendering.viewMask = (1 << renderingLayout->viewCount) - 1; + } + createInfo.pNext = &dynRendering; + + result = s_Vk.createGraphicsPipeline( + deviceImpl->handle, + 0, + 1, + &createInfo, + &s_Vk.allocatorImpl, + &pipeline->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, pipeline); + return makeResultVk(result); + } + + palFree(s_Vk.allocator, colorAttachments); + palFree(s_Vk.allocator, shaderStages); + if (info->vertexLayoutCount) { + palFree(s_Vk.allocator, bindingDescs); + palFree(s_Vk.allocator, attribDescs); + } + + if (info->colorBlendAttachmentCount) { + palFree(s_Vk.allocator, blendattachments); + } + + pipeline->bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + pipeline->device = deviceImpl; + pipeline->layout = layout->handle; + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createComputePipelineVk( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + DeviceVk* deviceImpl = (DeviceVk*)device; + PipelineLayoutVk* layout = (PipelineLayoutVk*)info->pipelineLayout; + ShaderVk* shader = (ShaderVk*)info->computeShader; + PipelineVk* pipeline = nullptr; + + pipeline = palAllocate(s_Vk.allocator, sizeof(PipelineVk), 0); + if (!pipeline) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkComputePipelineCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + createInfo.layout = layout->handle; + + createInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + createInfo.stage.module = shader->handle; + createInfo.stage.stage = shader->entries[0].stage; + createInfo.stage.pName = shader->entries[0].entryName; + + VkResult result = s_Vk.createComputePipeline( + deviceImpl->handle, + nullptr, + 1, + &createInfo, + &s_Vk.allocatorImpl, + &pipeline->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, pipeline); + return makeResultVk(result); + } + + pipeline->bindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; + pipeline->device = deviceImpl; + pipeline->layout = layout->handle; + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL createRayTracingPipelineVk( + PalDevice* device, + const PalRayTracingPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + PipelineLayoutVk* layout = (PipelineLayoutVk*)info->pipelineLayout; + PipelineVk* pipeline = nullptr; + VkPipelineShaderStageCreateInfo* shaderStages = nullptr; + VkRayTracingShaderGroupCreateInfoKHR* groups = nullptr; + + if (info->maxPayloadSize > deviceImpl->limits.maxPayloadSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + VkRayTracingPipelineCreateInfoKHR createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR; + + // find the total number of shader stages + uint32_t stageCount = 0; + for (int i = 0; i < info->shaderCount; i++) { + ShaderVk* shader = (ShaderVk*)info->shaders[i]; + stageCount += shader->entryCount; + } + + pipeline = palAllocate(s_Vk.allocator, sizeof(PipelineVk), 0); + groups = palAllocate( + s_Vk.allocator, + sizeof(VkRayTracingShaderGroupCreateInfoKHR) * info->shaderGroupCount, + 0); + + shaderStages = + palAllocate(s_Vk.allocator, sizeof(VkPipelineShaderStageCreateInfo) * stageCount, 0); + + if (!pipeline || !groups || !shaderStages) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(pipeline, 0, sizeof(PipelineVk)); + + // shaders + uint32_t stageIndex = 0; + memset(shaderStages, 0, sizeof(VkPipelineShaderStageCreateInfo) * stageCount); + for (int i = 0; i < info->shaderCount; i++) { + ShaderVk* tmp = (ShaderVk*)info->shaders[i]; + + for (int j = 0; j < tmp->entryCount; j++) { + ShaderEntry* entry = &tmp->entries[j]; + VkPipelineShaderStageCreateInfo* stageInfo = &shaderStages[stageIndex++]; + + stageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stageInfo->module = tmp->handle; + stageInfo->pName = entry->entryName; + stageInfo->stage = entry->stage; + } + } + + createInfo.stageCount = stageCount; + createInfo.pStages = shaderStages; + + ShaderBindingTableInfo* sbtInfo = &pipeline->sbtInfo; + for (int i = 0; i < info->shaderGroupCount; i++) { + VkRayTracingShaderGroupCreateInfoKHR* group = &groups[i]; + PalRayTracingShaderGroupCreateInfo* tmp = &info->shaderGroups[i]; + + group->sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; + group->pShaderGroupCaptureReplayHandle = nullptr; + group->pNext = nullptr; + + if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL) { + // check if its raygen, miss or callable + ShaderVk* shader = (ShaderVk*)info->shaders[tmp->generalShaderIndex]; + ShaderEntry* entry = &shader->entries[tmp->generalShaderEntryIndex]; + + switch (entry->stage) { + case VK_SHADER_STAGE_RAYGEN_BIT_KHR: { + sbtInfo->raygenCount++; + sbtInfo->raygenDataSize = max(sbtInfo->raygenDataSize, tmp->maxDataSize); + break; + } + + case VK_SHADER_STAGE_MISS_BIT_KHR: { + sbtInfo->missCount++; + sbtInfo->missDataSize = max(sbtInfo->missDataSize, tmp->maxDataSize); + break; + } + + case VK_SHADER_STAGE_CALLABLE_BIT_KHR: { + sbtInfo->callableCount++; + sbtInfo->callableDataSize = max(sbtInfo->callableDataSize, tmp->maxDataSize); + break; + } + } + + group->type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group->generalShader = tmp->generalShaderEntryIndex; + group->anyHitShader = VK_SHADER_UNUSED_KHR; + group->closestHitShader = VK_SHADER_UNUSED_KHR; + group->intersectionShader = VK_SHADER_UNUSED_KHR; + + } else { + sbtInfo->hitCount++; + sbtInfo->hitDataSize = max(sbtInfo->hitDataSize, tmp->maxDataSize); + + group->generalShader = VK_SHADER_UNUSED_KHR; + if (tmp->anyHitShaderIndex != PAL_UNUSED_SHADER_INDEX) { + group->anyHitShader = tmp->anyHitShaderEntryIndex; + + } else { + group->anyHitShader = VK_SHADER_UNUSED_KHR; + } + + if (tmp->closestHitShaderIndex != PAL_UNUSED_SHADER_INDEX) { + group->closestHitShader = tmp->closestHitShaderEntryIndex; + + } else { + group->closestHitShader = VK_SHADER_UNUSED_KHR; + } + + if (tmp->intersectionShaderIndex != PAL_UNUSED_SHADER_INDEX) { + group->intersectionShader = tmp->intersectionShaderEntryIndex; + + } else { + group->intersectionShader = VK_SHADER_UNUSED_KHR; + } + + if (tmp->type == PAL_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT) { + group->type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; + + } else { + group->type = VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR; + } + } + } + + createInfo.pGroups = groups; + createInfo.groupCount = info->shaderGroupCount; + createInfo.maxPipelineRayRecursionDepth = info->maxRecursionDepth; + createInfo.layout = layout->handle; + + result = deviceImpl->createRayTracingPipeline( + deviceImpl->handle, + nullptr, + nullptr, + 1, + &createInfo, + &s_Vk.allocatorImpl, + &pipeline->handle); + + palFree(s_Vk.allocator, groups); + palFree(s_Vk.allocator, shaderStages); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, pipeline); + return makeResultVk(result); + } + + pipeline->bindPoint = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR; + pipeline->device = deviceImpl; + pipeline->layout = layout->handle; + *outPipeline = (PalPipeline*)pipeline; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyPipelineVk(PalPipeline* pipeline) +{ + PipelineVk* pipelineImpl = (PipelineVk*)pipeline; + s_Vk.destroyPipeline(pipelineImpl->device->handle, pipelineImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, pipeline); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_sbt_vulkan.c b/src/graphics/vulkan/pal_sbt_vulkan.c new file mode 100644 index 00000000..68e530cd --- /dev/null +++ b/src/graphics/vulkan/pal_sbt_vulkan.c @@ -0,0 +1,389 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +#define _ALIGN(v, a) (v + a - 1) & ~(a - 1) + +PalResult PAL_CALL createShaderBindingTableVk( + PalDevice* device, + const PalShaderBindingTableCreateInfo* info, + PalShaderBindingTable** outSbt) +{ + VkResult result; + DeviceVk* deviceImpl = (DeviceVk*)device; + ShaderBindingTableVk* sbt = nullptr; + PipelineVk* pipeline = (PipelineVk*)info->rayTracingPipeline; + ShaderBindingTableInfo* sbtInfo = &pipeline->sbtInfo; + + uint32_t totalGroups = sbtInfo->raygenCount + sbtInfo->hitCount; + totalGroups += sbtInfo->missCount + sbtInfo->callableCount; + if (info->recordCount != totalGroups) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + sbt = palAllocate(s_Vk.allocator, sizeof(ShaderBindingTableVk), 0); + if (!sbt) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // create SBT buffer + VkPhysicalDeviceRayTracingPipelinePropertiesKHR rayProps = {0}; + rayProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR; + + VkPhysicalDeviceProperties2KHR props = {0}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; + props.pNext = &rayProps; + s_Vk.getPhysicalDeviceProperties2(deviceImpl->phyDevice, &props); + + uint32_t groupHandleSize = rayProps.shaderGroupHandleSize; + uint32_t groupHandleAlignment = rayProps.shaderGroupHandleAlignment; + uint32_t groupBaseAlignment = rayProps.shaderGroupBaseAlignment; + + // get the max local data size + for (int i = 0; i < info->recordCount; i++) { + PalShaderBindingTableRecordInfo* record = &info->records[i]; + uint32_t index = record->groupIndex; + + if (index < sbtInfo->raygenCount) { + // raygen group + if (record->localDataSize > sbtInfo->raygenDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount) { + // miss group + if (record->localDataSize > sbtInfo->missDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount) { + // hit group + if (record->localDataSize > sbtInfo->hitDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + } else { + // callable group + if (record->localDataSize > sbtInfo->callableDataSize) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + } + + // get strides + uint32_t callableStride = 0; + uint32_t raygenStride = _ALIGN(groupHandleSize + sbtInfo->raygenDataSize, groupHandleAlignment); + uint32_t missStride = _ALIGN(groupHandleSize + sbtInfo->missDataSize, groupHandleAlignment); + uint32_t hitStride = _ALIGN(groupHandleSize + sbtInfo->hitDataSize, groupHandleAlignment); + callableStride = _ALIGN(groupHandleSize + sbtInfo->callableDataSize, groupHandleAlignment); + + // get region size + uint32_t raygenRegionSize = raygenStride * sbtInfo->raygenCount; + uint32_t missRegionSize = missStride * sbtInfo->missCount; + uint32_t hitRegionSize = hitStride * sbtInfo->hitCount; + uint32_t callableRegionSize = callableStride * sbtInfo->callableCount; + + // get offsets + uint32_t offset = 0; + uint32_t raygenOffset = 0; + uint32_t missOffset = 0; + uint32_t hitOffset = 0; + uint32_t callableOffset = 0; + + raygenOffset = _ALIGN(offset, groupBaseAlignment); + offset = raygenOffset + raygenRegionSize; + + missOffset = _ALIGN(offset, groupBaseAlignment); + offset = missOffset + missRegionSize; + + hitOffset = _ALIGN(offset, groupBaseAlignment); + offset = hitOffset + hitRegionSize; + + callableOffset = _ALIGN(offset, groupBaseAlignment); + offset = callableOffset + callableRegionSize; + + uint32_t bufferSize = _ALIGN(offset, groupBaseAlignment); + + // create gpu buffer + VkBufferCreateInfo bufCreateInfo = {0}; + bufCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufCreateInfo.size = bufferSize; + bufCreateInfo.usage = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR; + bufCreateInfo.usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT_KHR; + bufCreateInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + bufCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + result = + s_Vk.createBuffer(deviceImpl->handle, &bufCreateInfo, &s_Vk.allocatorImpl, &sbt->buffer); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // create staging buffer + bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + sbt->stagingBufferSize = bufferSize; + result = s_Vk.createBuffer( + deviceImpl->handle, + &bufCreateInfo, + &s_Vk.allocatorImpl, + &sbt->stagingBuffer); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // allocate CPU upload memory and bind + VkMemoryRequirements memReq = {0}; + s_Vk.getBufferMemoryRequirements(deviceImpl->handle, sbt->buffer, &memReq); + + VkMemoryAllocateInfo allocateInfo = {0}; + allocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + allocateInfo.allocationSize = memReq.size; + + uint32_t mask = deviceImpl->memoryClassMask[PAL_MEMORY_TYPE_GPU_ONLY] & memReq.memoryTypeBits; + uint32_t memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, mask); + allocateInfo.memoryTypeIndex = memoryIndex; + + VkMemoryAllocateFlagsInfo allocateFlagsInfo = {0}; + allocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + allocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + allocateInfo.pNext = &allocateFlagsInfo; + + result = s_Vk.allocateMemory( + deviceImpl->handle, + &allocateInfo, + &s_Vk.allocatorImpl, + &sbt->bufferMemory); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // allocate memory for staging buffer + s_Vk.getBufferMemoryRequirements(deviceImpl->handle, sbt->stagingBuffer, &memReq); + allocateInfo.allocationSize = memReq.size; + + mask = deviceImpl->memoryClassMask[PAL_MEMORY_TYPE_CPU_UPLOAD] & memReq.memoryTypeBits; + memoryIndex = findBestMemoryIndexVk(deviceImpl->phyDevice, mask); + allocateInfo.memoryTypeIndex = memoryIndex; + allocateInfo.pNext = nullptr; // we dont need the address + + result = s_Vk.allocateMemory( + deviceImpl->handle, + &allocateInfo, + &s_Vk.allocatorImpl, + &sbt->stagingBufferMemory); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + s_Vk.bindBufferMemory(deviceImpl->handle, sbt->buffer, sbt->bufferMemory, 0); + s_Vk.bindBufferMemory(deviceImpl->handle, sbt->stagingBuffer, sbt->stagingBufferMemory, 0); + + // get shader group handles + uint32_t handlesSize = totalGroups * groupHandleSize; + uint8_t* handles = palAllocate(s_Vk.allocator, handlesSize, 0); + if (!handles) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + result = deviceImpl->getRayTracingShaderGroupHandles( + deviceImpl->handle, + pipeline->handle, + 0, + totalGroups, + handlesSize, + handles); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // copy handles into the buffer + void* ptr = nullptr; + result = + s_Vk.mapMemory(deviceImpl->handle, sbt->stagingBufferMemory, 0, VK_WHOLE_SIZE, 0, &ptr); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + offset = 0; // reuse variable + uint8_t* srcPtr = (uint8_t*)handles; + + // raygen + for (int i = 0; i < sbtInfo->raygenCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + (i * raygenStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, srcPtr + (i * groupHandleSize), groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + + offset += sbtInfo->raygenCount; + srcPtr += (groupHandleSize * sbtInfo->raygenCount); + + // miss + for (int i = 0; i < sbtInfo->missCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + missOffset + (i * missStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, srcPtr + (i * groupHandleSize), groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + + offset += sbtInfo->missCount; + srcPtr += (groupHandleSize * sbtInfo->missCount); + + // hit + for (int i = 0; i < sbtInfo->hitCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + hitOffset + (i * hitStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, srcPtr + (i * groupHandleSize), groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + + offset += sbtInfo->hitCount; + srcPtr += (groupHandleSize * sbtInfo->hitCount); + + // callable + for (int i = 0; i < sbtInfo->callableCount; i++) { + uint8_t* dstPtr = (uint8_t*)ptr + callableOffset + (i * callableStride); + PalShaderBindingTableRecordInfo* record = &info->records[offset + i]; + + memcpy(dstPtr, srcPtr + (i * groupHandleSize), groupHandleSize); + if (record->localDataSize) { + // this record has local data + memcpy(dstPtr + groupHandleSize, record->localData, record->localDataSize); + } + } + + sbt->stagingPtr = ptr; + // cache SBT fields and offsets address + // we know the layout so we can prepare the strided address before we copy to the gpu buffer + VkBufferDeviceAddressInfo bufferAddressInfo = {0}; + bufferAddressInfo.buffer = sbt->buffer; + bufferAddressInfo.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + sbt->baseAddress = s_Vk.getBufferDeviceAddress(deviceImpl->handle, &bufferAddressInfo); + + // raygen + sbt->raygen.region.deviceAddress = sbt->baseAddress; + sbt->raygen.offset = 0; // always + sbt->raygen.startIndex = 0; // always + sbt->raygen.region.size = raygenRegionSize; + sbt->raygen.region.stride = raygenStride; + + // miss + sbt->miss.offset = missOffset; + sbt->miss.startIndex = sbtInfo->raygenCount; + sbt->miss.region.deviceAddress = sbt->baseAddress + missOffset; + sbt->miss.region.size = missRegionSize; + sbt->miss.region.stride = missStride; + + // hit + sbt->hit.offset = hitOffset; + sbt->hit.startIndex = sbtInfo->raygenCount + sbtInfo->missCount; + sbt->hit.region.deviceAddress = sbt->baseAddress + hitOffset; + sbt->hit.region.size = hitRegionSize; + sbt->hit.region.stride = hitStride; + + // callable + sbt->callable.offset = callableOffset; + sbt->callable.startIndex = sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount; + sbt->callable.region.deviceAddress = sbt->baseAddress + callableOffset; + sbt->callable.region.size = callableRegionSize; + sbt->callable.region.stride = callableStride; + + palFree(s_Vk.allocator, handles); + sbt->device = deviceImpl; + sbt->handleSize = groupHandleSize; + sbt->pipeline = pipeline; + + sbt->isDirty = PAL_TRUE; // we need to copy from the staging to the gpu buffer + *outSbt = (PalShaderBindingTable*)sbt; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyShaderBindingTableVk(PalShaderBindingTable* sbt) +{ + ShaderBindingTableVk* sbtImpl = (ShaderBindingTableVk*)sbt; + DeviceVk* device = sbtImpl->device; + s_Vk.unmapMemory(device->handle, sbtImpl->stagingBufferMemory); + + s_Vk.destroyBuffer(device->handle, sbtImpl->buffer, &s_Vk.allocatorImpl); + s_Vk.destroyBuffer(device->handle, sbtImpl->stagingBuffer, &s_Vk.allocatorImpl); + s_Vk.freeMemory(device->handle, sbtImpl->bufferMemory, &s_Vk.allocatorImpl); + s_Vk.freeMemory(device->handle, sbtImpl->stagingBufferMemory, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, sbtImpl); +} + +void PAL_CALL updateShaderBindingTableVk( + PalShaderBindingTable* sbt, + uint32_t count, + PalShaderBindingTableRecordInfo* infos) +{ + ShaderBindingTableVk* sbtImpl = (ShaderBindingTableVk*)sbt; + DeviceVk* deviceImpl = sbtImpl->device; + PipelineVk* pipeline = sbtImpl->pipeline; + ShaderBindingTableInfo* sbtInfo = &pipeline->sbtInfo; + + uint32_t stride = 0; + uint32_t offset = 0; + uint32_t startIndex = 0; + + for (int i = 0; i < count; i++) { + PalShaderBindingTableRecordInfo* info = &infos[i]; + + // find the group the record belongs to + uint32_t index = info->groupIndex; + if (index < sbtInfo->raygenCount) { + // raygen group + offset = 0; + stride = sbtImpl->raygen.region.stride; + startIndex = sbtImpl->raygen.startIndex; + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount) { + // miss group + offset = sbtImpl->miss.offset; + stride = sbtImpl->miss.region.stride; + startIndex = sbtImpl->miss.startIndex; + + } else if (index < sbtInfo->raygenCount + sbtInfo->missCount + sbtInfo->hitCount) { + // hit group + offset = sbtImpl->hit.offset; + stride = sbtImpl->hit.region.stride; + startIndex = sbtImpl->hit.startIndex; + + } else { + // callable group + offset = sbtImpl->callable.offset; + stride = sbtImpl->callable.region.stride; + startIndex = sbtImpl->callable.startIndex; + } + + // write payload + uint32_t localIndex = index - startIndex; + uint8_t* dst = (uint8_t*)sbtImpl->stagingPtr + offset + (localIndex * stride); + memcpy(dst + sbtImpl->handleSize, info->localData, info->localDataSize); + } + + sbtImpl->isDirty = PAL_TRUE; +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_swapchain_vulkan.c b/src/graphics/vulkan/pal_swapchain_vulkan.c new file mode 100644 index 00000000..79248621 --- /dev/null +++ b/src/graphics/vulkan/pal_swapchain_vulkan.c @@ -0,0 +1,476 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +PalResult PAL_CALL createSurfaceVk( + PalDevice* device, + void* window, + void* windowInstance, + PalWindowInstanceType instanceType, + PalSurface** outSurface) +{ + VkResult result; + SurfaceVk* surface = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + VkSurfaceKHR tmp = nullptr; + + surface = palAllocate(s_Vk.allocator, sizeof(SurfaceVk), 0); + if (!surface) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + +#ifdef _WIN32 + if (instanceType != PAL_WINDOW_INSTANCE_TYPE_WIN32) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (!s_Vk.createWin32Surface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + VkWin32SurfaceCreateInfoKHR cInfo = {0}; + cInfo.hinstance = windowInstance; + cInfo.hwnd = window; + cInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + result = s_Vk.createWin32Surface(s_Vk.instance, &cInfo, &s_Vk.allocatorImpl, &tmp); + +#else + if (instanceType == PAL_WINDOW_INSTANCE_TYPE_WIN32) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (instanceType == PAL_WINDOW_INSTANCE_TYPE_WAYLAND) { + if (!s_Vk.createWaylandSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + VkWaylandSurfaceCreateInfoKHR cInfo = {0}; + cInfo.display = windowInstance; + cInfo.pNext = nullptr; + cInfo.flags = 0; + cInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + cInfo.surface = window; + result = s_Vk.createWaylandSurface(s_Vk.instance, &cInfo, &s_Vk.allocatorImpl, &tmp); + + } else if (instanceType == PAL_WINDOW_INSTANCE_TYPE_X11) { + if (!s_Vk.createXlibSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + VkXlibSurfaceCreateInfoKHR cInfo = {0}; + cInfo.dpy = windowInstance; + cInfo.window = (Window)(uintptr_t)(window); + cInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + result = s_Vk.createXlibSurface(s_Vk.instance, &cInfo, &s_Vk.allocatorImpl, &tmp); + + } else if (instanceType == PAL_WINDOW_INSTANCE_TYPE_XCB) { + if (!s_Vk.createXcbSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + VkXcbSurfaceCreateInfoKHR cInfo = {0}; + cInfo.connection = windowInstance; + cInfo.window = (xcb_window_t)(uintptr_t)(window); + cInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; + result = s_Vk.createXcbSurface(s_Vk.instance, &cInfo, &s_Vk.allocatorImpl, &tmp); + } +#endif // _WIN32 + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, surface); + return makeResultVk(result); + } + + surface->device = deviceImpl; + surface->handle = tmp; + *outSurface = (PalSurface*)surface; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySurfaceVk(PalSurface* surface) +{ + SurfaceVk* surfaceImpl = (SurfaceVk*)surface; + s_Vk.destroySurface(s_Vk.instance, surfaceImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, surfaceImpl); +} + +void PAL_CALL getSurfaceCapabilitiesVk( + PalDevice* device, + PalSurface* surface, + PalSurfaceCapabilities* caps) +{ + uint32_t formatCount = 0; + uint32_t modeCount = 0; + SurfaceVk* surfaceImpl = (SurfaceVk*)surface; + VkSurfaceFormatKHR* formats = nullptr; + VkPresentModeKHR* modes = nullptr; + + DeviceVk* deviceImpl = (DeviceVk*)device; + VkPhysicalDevice phyDevice = (VkPhysicalDevice)deviceImpl->phyDevice; + + memset(caps, 0, sizeof(PalSurfaceCapabilities)); + s_Vk.getSurfacePresentModes(phyDevice, surfaceImpl->handle, &modeCount, nullptr); + s_Vk.getSurfaceFormats(phyDevice, surfaceImpl->handle, &formatCount, nullptr); + + modes = palAllocate(s_Vk.allocator, sizeof(VkPresentModeKHR) * modeCount, 0); + formats = palAllocate(s_Vk.allocator, sizeof(VkSurfaceFormatKHR) * formatCount, 0); + if (!modes || !formats) { + return; + } + + s_Vk.getSurfacePresentModes(phyDevice, surfaceImpl->handle, &modeCount, modes); + s_Vk.getSurfaceFormats(phyDevice, surfaceImpl->handle, &formatCount, formats); + + VkSurfaceCapabilitiesKHR surfaceCaps; + s_Vk.getSurfaceCapabilities(phyDevice, surfaceImpl->handle, &surfaceCaps); + caps->minImageWidth = surfaceCaps.minImageExtent.width; + caps->minImageHeight = surfaceCaps.minImageExtent.height; + caps->maxImageWidth = surfaceCaps.maxImageExtent.width; + caps->maxImageHeight = surfaceCaps.maxImageExtent.height; + + caps->maxImageCount = surfaceCaps.maxImageCount; + caps->minImageCount = surfaceCaps.minImageCount; + caps->maxImageArrayLayers = surfaceCaps.maxImageArrayLayers; + + if (caps->maxImageCount == 0) { + caps->maxImageCount = 8; // safe + } + + // get supported composite alphas + VkCompositeAlphaFlagsKHR alpha = surfaceCaps.supportedCompositeAlpha; + caps->supportedCompositeAlphas |= (1u << PAL_COMPOSITE_ALPHA_OPAQUE); + if (alpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR) { + caps->supportedCompositeAlphas |= (1u << PAL_COMPOSITE_ALPHA_POST_MULTIPLIED); + } + + if (alpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR) { + caps->supportedCompositeAlphas |= (1u << PAL_COMPOSITE_ALPHA_PRE_MULTIPLIED); + } + + // present modes + caps->supportedPresentModes |= (1u << PAL_PRESENT_MODE_FIFO); + for (int i = 0; i < modeCount; i++) { + if (modes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) { + caps->supportedPresentModes |= (1u << PAL_PRESENT_MODE_IMMEDIATE); + } + + if (modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { + caps->supportedPresentModes |= (1u << PAL_PRESENT_MODE_MAILBOX); + } + } + + // get format and colorspace + for (int i = 0; i < formatCount; i++) { + VkSurfaceFormatKHR* fmt = &formats[i]; + if (fmt->format == VK_FORMAT_B8G8R8A8_UNORM) { + // find its supported colorspace + if (fmt->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR); + } + + } else if (fmt->format == VK_FORMAT_B8G8R8A8_SRGB) { + // find its supported colorspace + if (fmt->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_BGRA8_SRGB_NONLINEAR); + } + + } else if (fmt->format == VK_FORMAT_R8G8B8A8_UNORM) { + // find its supported colorspace + if (fmt->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_RGBA8_UNORM_SRGB_NONLINEAR); + } + + } else if (fmt->format == VK_FORMAT_R16G16B16A16_SFLOAT) { + // find its supported colorspace + if (fmt->colorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT) { + caps->supportedFormats |= (1u << PAL_SURFACE_FORMAT_RGBA16_FLOAT_HDR10); + } + } + } + + palFree(s_Vk.allocator, formats); + palFree(s_Vk.allocator, modes); +} + +PalResult PAL_CALL createSwapchainVk( + PalDevice* device, + PalQueue* queue, + PalSurface* surface, + const PalSwapchainCreateInfo* info, + PalSwapchain** outSwapchain) +{ + PalFormat imageFormat = 0; + SwapchainVk* swapchain = nullptr; + VkImage* images = nullptr; + + DeviceVk* deviceImpl = (DeviceVk*)device; + QueueVk* queueImpl = (QueueVk*)queue; + PhysicalQueue* phyQueue = queueImpl->phyQueue; + SurfaceVk* surfaceImpl = (SurfaceVk*)surface; + + // check if the queue is a graphics queue before we check its family + // index for presentation support. + if (queueImpl->usage != VK_QUEUE_GRAPHICS_BIT) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + swapchain = palAllocate(s_Vk.allocator, sizeof(SwapchainVk), 0); + if (!swapchain) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + memset(swapchain, 0, sizeof(SwapchainVk)); + + VkSwapchainCreateInfoKHR createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.surface = surfaceImpl->handle; + createInfo.imageArrayLayers = info->imageArrayLayerCount; + createInfo.imageExtent.width = info->width; + createInfo.imageExtent.height = info->height; + createInfo.minImageCount = info->imageCount; + createInfo.clipped = info->clipped; + createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; + + // present modes + createInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; + if (info->presentMode == PAL_PRESENT_MODE_IMMEDIATE) { + createInfo.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; + + } else if (info->presentMode == PAL_PRESENT_MODE_MAILBOX) { + createInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR; + } + + // composite alpha + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + if (info->compositeAlpha == PAL_COMPOSITE_ALPHA_POST_MULTIPLIED) { + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; + + } else if (info->compositeAlpha == PAL_COMPOSITE_ALPHA_PRE_MULTIPLIED) { + createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; + } + + // format and colorspace + createInfo.imageFormat = VK_FORMAT_B8G8R8A8_UNORM; + createInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + imageFormat = PAL_FORMAT_B8G8R8A8_UNORM; + + if (info->format == PAL_SURFACE_FORMAT_BGRA8_SRGB_NONLINEAR) { + createInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB; + createInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + imageFormat = PAL_FORMAT_B8G8R8A8_SRGB; + + } else if (info->format == PAL_SURFACE_FORMAT_RGBA8_UNORM_SRGB_NONLINEAR) { + createInfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM; + createInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + imageFormat = PAL_FORMAT_R8G8B8A8_UNORM; + + } else if (info->format == PAL_SURFACE_FORMAT_RGBA16_FLOAT_HDR10) { + createInfo.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + createInfo.imageColorSpace = VK_COLOR_SPACE_HDR10_ST2084_EXT; + imageFormat = PAL_FORMAT_R16G16B16A16_SFLOAT; + } + + // create swapchain + VkResult result = deviceImpl->createSwapchain( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &swapchain->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, swapchain); + return makeResultVk(result); + } + + // get and cache all images + uint32_t count = 0; + result = deviceImpl->getSwapchainImages(deviceImpl->handle, swapchain->handle, &count, nullptr); + + swapchain->images = palAllocate(s_Vk.allocator, sizeof(ImageVk) * count, 0); + images = palAllocate(s_Vk.allocator, sizeof(VkImage) * count, 0); + if (!swapchain->images || !images) { + deviceImpl->destroySwapchain(deviceImpl->handle, swapchain->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, swapchain); + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + deviceImpl->getSwapchainImages(deviceImpl->handle, swapchain->handle, &count, images); + + // fill all images with the creation info + for (int i = 0; i < count; i++) { + ImageVk* image = &swapchain->images[i]; + image->device = deviceImpl; + image->handle = images[i]; + + image->info.belongsToSwapchain = PAL_TRUE; + image->info.arrayLayerCount = createInfo.imageArrayLayers; + image->info.depth = 1; + image->info.format = imageFormat; + image->info.usages = PAL_IMAGE_USAGE_COLOR_ATTACHEMENT; + image->info.height = createInfo.imageExtent.height; + image->info.width = createInfo.imageExtent.width; + image->info.mipLevelCount = 1; + image->info.sampleCount = PAL_SAMPLE_COUNT_1; // swapchain images are not multisampled + image->info.type = PAL_IMAGE_TYPE_2D; + } + palFree(s_Vk.allocator, images); + + swapchain->device = deviceImpl; + swapchain->queue = queueImpl; + swapchain->imageCount = count; + *outSwapchain = (PalSwapchain*)swapchain; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySwapchainVk(PalSwapchain* swapchain) +{ + SwapchainVk* swapchainImpl = (SwapchainVk*)swapchain; + swapchainImpl->device->destroySwapchain( + swapchainImpl->device->handle, + swapchainImpl->handle, + &s_Vk.allocatorImpl); + + palFree(s_Vk.allocator, swapchainImpl->images); + palFree(s_Vk.allocator, swapchainImpl); +} + +PalImage* PAL_CALL getSwapchainImageVk( + PalSwapchain* swapchain, + uint32_t index) +{ + SwapchainVk* swapchainImpl = (SwapchainVk*)swapchain; + return (PalImage*)&swapchainImpl->images[index]; +} + +PalResult PAL_CALL getNextSwapchainImageVk( + PalSwapchain* swapchain, + PalSwapchainNextImageInfo* info, + uint32_t* outIndex) +{ + VkResult result; + uint32_t index = 0; + uint64_t timeInNanoseconds = 0; + VkFence fenceHandle = nullptr; + VkSemaphore semaphoreHandle = nullptr; + SwapchainVk* swapchainImpl = (SwapchainVk*)swapchain; + + if (info->fence) { + FenceVk* fenceImpl = (FenceVk*)info->fence; + fenceHandle = fenceImpl->handle; + } + + if (info->signalSemaphore) { + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)info->signalSemaphore; + semaphoreHandle = semaphoreImpl->handle; + } + + if (info->timeout) { + if (info->timeout == PAL_INFINITE) { + timeInNanoseconds = UINT64_MAX; + } else { + timeInNanoseconds = info->timeout * 1000000; + } + } + + result = swapchainImpl->device->acquireNextImage( + swapchainImpl->device->handle, + swapchainImpl->handle, + timeInNanoseconds, + semaphoreHandle, + fenceHandle, + &index); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + *outIndex = index; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL presentSwapchainVk( + PalSwapchain* swapchain, + uint32_t imageIndex, + PalSemaphore* waitSemaphore) +{ + SwapchainVk* swapchainImpl = (SwapchainVk*)swapchain; + int32_t semaphoreCount = 0; + VkSemaphore semaphoreHandle = nullptr; + if (waitSemaphore) { + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)waitSemaphore; + semaphoreHandle = semaphoreImpl->handle; + semaphoreCount = 1; + } + + VkResult result; + VkPresentInfoKHR presentInfo = {0}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapchainImpl->handle; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pWaitSemaphores = &semaphoreHandle; + presentInfo.waitSemaphoreCount = semaphoreCount; + + result = + swapchainImpl->device->queuePresent(swapchainImpl->queue->phyQueue->handle, &presentInfo); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL resizeSwapchainVk( + PalSwapchain* swapchain, + uint32_t newWidth, + uint32_t newHeight) +{ + VkResult result; + SwapchainVk* swapchainImpl = (SwapchainVk*)swapchain; + VkSwapchainKHR oldSwapchain = swapchainImpl->handle; + DeviceVk* device = swapchainImpl->device; + VkImage* images = nullptr; + + VkSwapchainCreateInfoKHR createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + createInfo.oldSwapchain = oldSwapchain; + createInfo.imageExtent.width = newWidth; + createInfo.imageExtent.height = newHeight; + + result = device->createSwapchain( + device->handle, + &createInfo, + &s_Vk.allocatorImpl, + &swapchainImpl->handle); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + uint32_t count = swapchainImpl->imageCount; + images = palAllocate(s_Vk.allocator, sizeof(VkImage) * count, 0); + if (!images) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + device->destroySwapchain(device->handle, oldSwapchain, &s_Vk.allocatorImpl); + device->getSwapchainImages(device->handle, swapchainImpl->handle, &count, images); + // fill all images with the new create info + for (int i = 0; i < count; i++) { + ImageVk* image = &swapchainImpl->images[i]; + image->handle = images[i]; + image->info.height = createInfo.imageExtent.height; + image->info.width = createInfo.imageExtent.width; + } + + palFree(s_Vk.allocator, images); + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_sync_vulkan.c b/src/graphics/vulkan/pal_sync_vulkan.c new file mode 100644 index 00000000..f78c6bda --- /dev/null +++ b/src/graphics/vulkan/pal_sync_vulkan.c @@ -0,0 +1,227 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" + +PalResult PAL_CALL createFenceVk( + PalDevice* device, + PalBool signaled, + PalFence** outFence) +{ + VkResult result; + FenceVk* fence = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + + fence = palAllocate(s_Vk.allocator, sizeof(FenceVk), 0); + if (!fence) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkFenceCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + if (signaled) { + createInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + } + + result = s_Vk.createFence(deviceImpl->handle, &createInfo, &s_Vk.allocatorImpl, &fence->handle); + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, fence); + return makeResultVk(result); + } + + fence->device = deviceImpl; + *outFence = (PalFence*)fence; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroyFenceVk(PalFence* fence) +{ + FenceVk* fenceImpl = (FenceVk*)fence; + s_Vk.destroyFence(fenceImpl->device->handle, fenceImpl->handle, &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, fenceImpl); +} + +PalResult PAL_CALL waitFenceVk( + PalFence* fence, + uint64_t timeout) +{ + FenceVk* fenceImpl = (FenceVk*)fence; + VkResult result; + uint64_t timeInNano = 0; + if (timeout) { + if (timeout == PAL_INFINITE) { + timeInNano = UINT64_MAX; + } else { + timeInNano = timeout * 1000000; + } + } + + result = s_Vk.waitFence(fenceImpl->device->handle, 1, &fenceImpl->handle, PAL_TRUE, timeInNano); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL resetFenceVk(PalFence* fence) +{ + FenceVk* fenceImpl = (FenceVk*)fence; + VkResult result = s_Vk.resetFence(fenceImpl->device->handle, 1, &fenceImpl->handle); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalBool PAL_CALL isFenceSignaledVk(PalFence* fence) +{ + FenceVk* fenceImpl = (FenceVk*)fence; + VkResult result = s_Vk.isFenceSignaled(fenceImpl->device->handle, fenceImpl->handle); + if (result == VK_SUCCESS) { + return PAL_TRUE; + + } else { + return PAL_FALSE; + } +} + +PalResult PAL_CALL createSemaphoreVk( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore) +{ + VkResult result; + SemaphoreVk* semaphore = nullptr; + DeviceVk* deviceImpl = (DeviceVk*)device; + + semaphore = palAllocate(s_Vk.allocator, sizeof(SemaphoreVk), 0); + if (!semaphore) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + VkSemaphoreTypeCreateInfo timelineCreateInfo = {0}; + timelineCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + + VkSemaphoreCreateInfo createInfo = {0}; + createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + const void* next = nullptr; + semaphore->isTimeline = PAL_FALSE; + if (enableTimeline) { + next = &timelineCreateInfo; + semaphore->isTimeline = PAL_TRUE; + } + + createInfo.pNext = next; + result = s_Vk.createSemaphore( + deviceImpl->handle, + &createInfo, + &s_Vk.allocatorImpl, + &semaphore->handle); + + if (result != VK_SUCCESS) { + palFree(s_Vk.allocator, semaphore); + return makeResultVk(result); + } + + semaphore->device = deviceImpl; + *outSemaphore = (PalSemaphore*)semaphore; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL destroySemaphoreVk(PalSemaphore* semaphore) +{ + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)semaphore; + s_Vk.destroySemaphore( + semaphoreImpl->device->handle, + semaphoreImpl->handle, + &s_Vk.allocatorImpl); + palFree(s_Vk.allocator, semaphoreImpl); +} + +PalResult PAL_CALL waitSemaphoreVk( + PalSemaphore* semaphore, + uint64_t value, + uint64_t timeout) +{ + VkResult result; + uint64_t timeInNano = 0; + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)semaphore; + if (!semaphoreImpl->isTimeline) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + if (timeout) { + if (timeout == PAL_INFINITE) { + timeInNano = UINT64_MAX; + } else { + timeInNano = timeout * 1000000; + } + } + + VkSemaphoreWaitInfo waitInfo = {0}; + waitInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + waitInfo.semaphoreCount = 1; + waitInfo.pSemaphores = &semaphoreImpl->handle; + waitInfo.pValues = &value; + + result = + semaphoreImpl->device->waitSemaphore(semaphoreImpl->device->handle, &waitInfo, timeInNano); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL signalSemaphoreVk( + PalSemaphore* semaphore, + PalQueue* queue, + uint64_t value) +{ + VkResult result; + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)semaphore; + if (!semaphoreImpl->isTimeline) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + VkSemaphoreSignalInfo signalInfo = {0}; + signalInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SIGNAL_INFO; + signalInfo.semaphore = semaphoreImpl->handle; + signalInfo.value = value; + + result = semaphoreImpl->device->signalSemaphore(semaphoreImpl->device->handle, &signalInfo); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL getSemaphoreValueVk( + PalSemaphore* semaphore, + uint64_t* value) +{ + SemaphoreVk* semaphoreImpl = (SemaphoreVk*)semaphore; + VkResult result = semaphoreImpl->device->getSemaphoreValue( + semaphoreImpl->device->handle, + semaphoreImpl->handle, + value); + + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_vulkan.c b/src/graphics/vulkan/pal_vulkan.c new file mode 100644 index 00000000..5269db87 --- /dev/null +++ b/src/graphics/vulkan/pal_vulkan.c @@ -0,0 +1,1638 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_VULKAN_BACKEND +#include "pal_vulkan.h" +#include "pal_platform.h" +#include + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include +#define VK_LIB_NAME "vulkan-1.dll" +#define RESULT_SOURCE PAL_RESULT_SOURCE_WIN32 +#elif defined(__linux__) +#include +#define VK_LIB_NAME "libvulkan.so" +#define RESULT_SOURCE PAL_RESULT_SOURCE_POSIX +#else +// Android +#define VK_LIB_NAME "" +#endif // _WIN32 + +#if _PAL_HAS_POSIX +#include +#endif // _PAL_HAS_POSIX + +Vulkan s_Vk = {0}; + +PalResult makeResultVk(VkResult result) +{ + PalResultCode code = PAL_RESULT_CODE_PLATFORM_FAILURE; + switch (result) { + case VK_ERROR_FEATURE_NOT_PRESENT: + case VK_ERROR_EXTENSION_NOT_PRESENT: { + code = PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + break; + } + + case VK_ERROR_OUT_OF_HOST_MEMORY: + case VK_ERROR_TOO_MANY_OBJECTS: + case VK_ERROR_OUT_OF_DEVICE_MEMORY: { + code = PAL_RESULT_CODE_OUT_OF_MEMORY; + break; + } + + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: { + code = PAL_RESULT_CODE_INVALID_HANDLE; + break; + } + + case VK_TIMEOUT: { + code = PAL_RESULT_CODE_TIMEOUT; + break; + } + + case VK_ERROR_MEMORY_MAP_FAILED: { + code = PAL_RESULT_CODE_INVALID_OPERATION; + break; + } + + case VK_ERROR_DEVICE_LOST: { + code = PAL_RESULT_CODE_DEVICE_LOST; + break; + } + + case VK_ERROR_OUT_OF_DATE_KHR: { + code = PAL_RESULT_CODE_OUT_OF_DATE; + break; + } + } + + return palMakeResult(code, PAL_RESULT_SOURCE_VULKAN, (uint32_t)result); +} + +VkFormat formatToVk(PalFormat format) +{ + switch (format) { + case PAL_FORMAT_R8_UNORM: + return VK_FORMAT_R8_UNORM; + + case PAL_FORMAT_R8_SNORM: + return VK_FORMAT_R8_SNORM; + + case PAL_FORMAT_R8_UINT: + return VK_FORMAT_R8_UINT; + + case PAL_FORMAT_R8_SINT: + return VK_FORMAT_R8_SINT; + + case PAL_FORMAT_R8_SRGB: + return VK_FORMAT_R8_SRGB; + + case PAL_FORMAT_R16_UNORM: + return VK_FORMAT_R16_UNORM; + + case PAL_FORMAT_R16_SNORM: + return VK_FORMAT_R16_SNORM; + + case PAL_FORMAT_R16_UINT: + return VK_FORMAT_R16_UINT; + + case PAL_FORMAT_R16_SINT: + return VK_FORMAT_R16_SINT; + + case PAL_FORMAT_R16_SFLOAT: + return VK_FORMAT_R16_SFLOAT; + + case PAL_FORMAT_R32_UINT: + return VK_FORMAT_R32_UINT; + + case PAL_FORMAT_R32_SINT: + return VK_FORMAT_R32_SINT; + + case PAL_FORMAT_R32_SFLOAT: + return VK_FORMAT_R32_SFLOAT; + + case PAL_FORMAT_R64_UINT: + return VK_FORMAT_R64_UINT; + + case PAL_FORMAT_R64_SINT: + return VK_FORMAT_R64_SINT; + + case PAL_FORMAT_R64_SFLOAT: + return VK_FORMAT_R64_SFLOAT; + + case PAL_FORMAT_R8G8_UNORM: + return VK_FORMAT_R8G8_UNORM; + + case PAL_FORMAT_R8G8_SNORM: + return VK_FORMAT_R8G8_SNORM; + + case PAL_FORMAT_R8G8_UINT: + return VK_FORMAT_R8G8_UINT; + + case PAL_FORMAT_R8G8_SINT: + return VK_FORMAT_R8G8_SINT; + + case PAL_FORMAT_R8G8_SRGB: + return VK_FORMAT_R8G8_SRGB; + + case PAL_FORMAT_R16G16_UNORM: + return VK_FORMAT_R16G16_UNORM; + + case PAL_FORMAT_R16G16_SNORM: + return VK_FORMAT_R16G16_SNORM; + + case PAL_FORMAT_R16G16_UINT: + return VK_FORMAT_R16G16_UINT; + + case PAL_FORMAT_R16G16_SINT: + return VK_FORMAT_R16G16_SINT; + + case PAL_FORMAT_R16G16_SFLOAT: + return VK_FORMAT_R16G16_SFLOAT; + + case PAL_FORMAT_R32G32_UINT: + return VK_FORMAT_R32G32_UINT; + + case PAL_FORMAT_R32G32_SINT: + return VK_FORMAT_R32G32_SINT; + + case PAL_FORMAT_R32G32_SFLOAT: + return VK_FORMAT_R32G32_SFLOAT; + + case PAL_FORMAT_R64G64_UINT: + return VK_FORMAT_R64G64_UINT; + + case PAL_FORMAT_R64G64_SINT: + return VK_FORMAT_R64G64_SINT; + + case PAL_FORMAT_R64G64_SFLOAT: + return VK_FORMAT_R64G64_SFLOAT; + + case PAL_FORMAT_R8G8B8_UNORM: + return VK_FORMAT_R8G8B8_UNORM; + + case PAL_FORMAT_R8G8B8_SNORM: + return VK_FORMAT_R8G8B8_SNORM; + + case PAL_FORMAT_R8G8B8_UINT: + return VK_FORMAT_R8G8B8_UINT; + + case PAL_FORMAT_R8G8B8_SINT: + return VK_FORMAT_R8G8B8_SINT; + + case PAL_FORMAT_R8G8B8_SRGB: + return VK_FORMAT_R8G8B8_SRGB; + + case PAL_FORMAT_R16G16B16_UNORM: + return VK_FORMAT_R16G16B16_UNORM; + + case PAL_FORMAT_R16G16B16_SNORM: + return VK_FORMAT_R16G16B16_SNORM; + + case PAL_FORMAT_R16G16B16_UINT: + return VK_FORMAT_R16G16B16_UINT; + + case PAL_FORMAT_R16G16B16_SINT: + return VK_FORMAT_R16G16B16_SINT; + + case PAL_FORMAT_R16G16B16_SFLOAT: + return VK_FORMAT_R16G16B16_SFLOAT; + + case PAL_FORMAT_R32G32B32_UINT: + return VK_FORMAT_R32G32B32_UINT; + + case PAL_FORMAT_R32G32B32_SINT: + return VK_FORMAT_R32G32B32_SINT; + + case PAL_FORMAT_R32G32B32_SFLOAT: + return VK_FORMAT_R32G32B32_SFLOAT; + + case PAL_FORMAT_R64G64B64_UINT: + return VK_FORMAT_R64G64B64_UINT; + + case PAL_FORMAT_R64G64B64_SINT: + return VK_FORMAT_R64G64B64_SINT; + + case PAL_FORMAT_R64G64B64_SFLOAT: + return VK_FORMAT_R64G64B64_SFLOAT; + + case PAL_FORMAT_B8G8R8_UNORM: + return VK_FORMAT_B8G8R8_UNORM; + + case PAL_FORMAT_B8G8R8_SNORM: + return VK_FORMAT_B8G8R8_SNORM; + + case PAL_FORMAT_B8G8R8_UINT: + return VK_FORMAT_B8G8R8_UINT; + + case PAL_FORMAT_B8G8R8_SINT: + return VK_FORMAT_B8G8R8_SINT; + + case PAL_FORMAT_B8G8R8_SRGB: + return VK_FORMAT_B8G8R8_SRGB; + + case PAL_FORMAT_R8G8B8A8_UNORM: + return VK_FORMAT_R8G8B8A8_UNORM; + + case PAL_FORMAT_R8G8B8A8_SNORM: + return VK_FORMAT_R8G8B8A8_SNORM; + + case PAL_FORMAT_R8G8B8A8_UINT: + return VK_FORMAT_R8G8B8A8_UINT; + + case PAL_FORMAT_R8G8B8A8_SINT: + return VK_FORMAT_R8G8B8A8_SINT; + + case PAL_FORMAT_R8G8B8A8_SRGB: + return VK_FORMAT_R8G8B8A8_SRGB; + + case PAL_FORMAT_R16G16B16A16_UNORM: + return VK_FORMAT_R16G16B16A16_UNORM; + + case PAL_FORMAT_R16G16B16A16_SNORM: + return VK_FORMAT_R16G16B16A16_SNORM; + + case PAL_FORMAT_R16G16B16A16_UINT: + return VK_FORMAT_R16G16B16A16_UINT; + + case PAL_FORMAT_R16G16B16A16_SINT: + return VK_FORMAT_R16G16B16A16_SINT; + + case PAL_FORMAT_R16G16B16A16_SFLOAT: + return VK_FORMAT_R16G16B16A16_SFLOAT; + + case PAL_FORMAT_R32G32B32A32_UINT: + return VK_FORMAT_R32G32B32A32_UINT; + + case PAL_FORMAT_R32G32B32A32_SINT: + return VK_FORMAT_R32G32B32A32_SINT; + + case PAL_FORMAT_R32G32B32A32_SFLOAT: + return VK_FORMAT_R32G32B32A32_SFLOAT; + + case PAL_FORMAT_R64G64B64A64_UINT: + return VK_FORMAT_R64G64B64A64_UINT; + + case PAL_FORMAT_R64G64B64A64_SINT: + return VK_FORMAT_R64G64B64A64_SINT; + + case PAL_FORMAT_R64G64B64A64_SFLOAT: + return VK_FORMAT_R64G64B64A64_SFLOAT; + + case PAL_FORMAT_B8G8R8A8_UNORM: + return VK_FORMAT_B8G8R8A8_UNORM; + + case PAL_FORMAT_B8G8R8A8_SNORM: + return VK_FORMAT_B8G8R8A8_SNORM; + + case PAL_FORMAT_B8G8R8A8_UINT: + return VK_FORMAT_B8G8R8A8_UINT; + + case PAL_FORMAT_B8G8R8A8_SINT: + return VK_FORMAT_B8G8R8A8_SINT; + + case PAL_FORMAT_B8G8R8A8_SRGB: + return VK_FORMAT_B8G8R8A8_SRGB; + + case PAL_FORMAT_S8_UINT: + return VK_FORMAT_S8_UINT; + + case PAL_FORMAT_D16_UNORM: + return VK_FORMAT_D16_UNORM; + + case PAL_FORMAT_D32_SFLOAT: + return VK_FORMAT_D32_SFLOAT; + + case PAL_FORMAT_D32_SFLOAT_S8_UINT: + return VK_FORMAT_D32_SFLOAT_S8_UINT; + + case PAL_FORMAT_D16_UNORM_S8_UINT: + return VK_FORMAT_D16_UNORM_S8_UINT; + + case PAL_FORMAT_D24_UNORM_S8_UINT: + return VK_FORMAT_D24_UNORM_S8_UINT; + } + + return VK_FORMAT_UNDEFINED; +} + +VkSampleCountFlags samplesToVk(PalSampleCount count) +{ + switch (count) { + case PAL_SAMPLE_COUNT_2: + return VK_SAMPLE_COUNT_2_BIT; + + case PAL_SAMPLE_COUNT_4: + return VK_SAMPLE_COUNT_4_BIT; + + case PAL_SAMPLE_COUNT_8: + return VK_SAMPLE_COUNT_8_BIT; + + case PAL_SAMPLE_COUNT_16: + return VK_SAMPLE_COUNT_16_BIT; + + case PAL_SAMPLE_COUNT_32: + return VK_SAMPLE_COUNT_32_BIT; + + case PAL_SAMPLE_COUNT_64: + return VK_SAMPLE_COUNT_64_BIT; + } + + return VK_SAMPLE_COUNT_1_BIT; +} + +VkExtent2D getShadingRateSizeVk(PalFragmentShadingRate rate) +{ + switch (rate) { + case PAL_FRAGMENT_SHADING_RATE_1X1: + return (VkExtent2D){1, 1}; + + case PAL_FRAGMENT_SHADING_RATE_1X2: + return (VkExtent2D){1, 2}; + + case PAL_FRAGMENT_SHADING_RATE_2X1: + return (VkExtent2D){2, 1}; + + case PAL_FRAGMENT_SHADING_RATE_2X2: + return (VkExtent2D){2, 2}; + + case PAL_FRAGMENT_SHADING_RATE_2X4: + return (VkExtent2D){2, 4}; + + case PAL_FRAGMENT_SHADING_RATE_4X2: + return (VkExtent2D){4, 2}; + + case PAL_FRAGMENT_SHADING_RATE_4X4: + return (VkExtent2D){4, 4}; + } + + return (VkExtent2D){0, 0}; +} + +VkFragmentShadingRateCombinerOpKHR combinerOpsToVk(PalFragmentShadingRateCombinerOp op) +{ + switch (op) { + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP: + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE: + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_KHR; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN: + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_KHR; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX: + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_KHR; + + case PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL: + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_KHR; + } + + return VK_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_KHR; +} + +VkImageAspectFlags imageAspectToVk(PalImageAspect aspect) +{ + switch (aspect) { + case PAL_IMAGE_ASPECT_COLOR: + return VK_IMAGE_ASPECT_COLOR_BIT; + + case PAL_IMAGE_ASPECT_DEPTH: + return VK_IMAGE_ASPECT_DEPTH_BIT; + + case PAL_IMAGE_ASPECT_STENCIL: + return VK_IMAGE_ASPECT_STENCIL_BIT; + + case PAL_IMAGE_ASPECT_DEPTH_STENCIL: + return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + } + + return VK_IMAGE_ASPECT_COLOR_BIT; +} + +VkStencilOp stencilOpToVk(PalStencilOp op) +{ + switch (op) { + case PAL_STENCIL_OP_KEEP: + return VK_STENCIL_OP_KEEP; + + case PAL_STENCIL_OP_ZERO: + return VK_STENCIL_OP_ZERO; + + case PAL_STENCIL_OP_REPLACE: + return VK_STENCIL_OP_REPLACE; + + case PAL_STENCIL_OP_INCREMENT_AND_CLAMP: + return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + + case PAL_STENCIL_OP_DECREMENT_AND_CLAMP: + return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + + case PAL_STENCIL_OP_INVERT: + return VK_STENCIL_OP_INVERT; + + case PAL_STENCIL_OP_INCREMENT_AND_WRAP: + return VK_STENCIL_OP_INCREMENT_AND_WRAP; + + case PAL_STENCIL_OP_DECREMENT_AND_WRAP: + return VK_STENCIL_OP_DECREMENT_AND_WRAP; + } + + return VK_STENCIL_OP_KEEP; +} + +VkCompareOp compareOpToVk(PalCompareOp op) +{ + switch (op) { + case PAL_COMPARE_OP_NEVER: + return VK_COMPARE_OP_NEVER; + + case PAL_COMPARE_OP_LESS: + return VK_COMPARE_OP_LESS; + + case PAL_COMPARE_OP_EQUAL: + return VK_COMPARE_OP_EQUAL; + + case PAL_COMPARE_OP_LESS_OR_EQUAL: + return VK_COMPARE_OP_LESS_OR_EQUAL; + + case PAL_COMPARE_OP_GREATER: + return VK_COMPARE_OP_GREATER; + + case PAL_COMPARE_OP_NOT_EQUAL: + return VK_COMPARE_OP_NOT_EQUAL; + + case PAL_COMPARE_OP_GREATER_OR_EQUAL: + return VK_COMPARE_OP_GREATER_OR_EQUAL; + + case PAL_COMPARE_OP_ALWAYS: + return VK_COMPARE_OP_ALWAYS; + } + + return VK_COMPARE_OP_NEVER; +} + +uint32_t findBestMemoryIndexVk( + VkPhysicalDevice phyDevice, + uint32_t memoryMask) +{ + int bestScore = -1; + uint32_t bestIndex = UINT32_MAX; + VkPhysicalDeviceMemoryProperties memProps = {0}; + s_Vk.getPhysicalDeviceMemoryProperties(phyDevice, &memProps); + + for (int i = 0; i < memProps.memoryTypeCount; i++) { + if (!(memoryMask & (1u << i))) { + continue; + } + + int score = 0; + VkMemoryPropertyFlags flags = memProps.memoryTypes[i].propertyFlags; + + // GPU only memory + if (flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) { + score += 100; + } + + // CPU memory + if (flags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { + score += 50; + } + + if (flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) { + score += 25; + } + + if (flags & VK_MEMORY_PROPERTY_HOST_CACHED_BIT) { + score += 10; + } + + if (score > bestScore) { + bestIndex = i; + } + } + + return bestIndex; +} + +VkFormat vertexTypeToVk(PalVertexType type) +{ + switch (type) { + case PAL_VERTEX_TYPE_INT32: + return VK_FORMAT_R32_SINT; + + case PAL_VERTEX_TYPE_INT32_2: + return VK_FORMAT_R32G32_SINT; + + case PAL_VERTEX_TYPE_INT32_3: + return VK_FORMAT_R32G32B32_SINT; + + case PAL_VERTEX_TYPE_INT32_4: + return VK_FORMAT_R32G32B32A32_SINT; + + case PAL_VERTEX_TYPE_UINT32: + return VK_FORMAT_R32_UINT; + + case PAL_VERTEX_TYPE_UINT32_2: + return VK_FORMAT_R32G32_UINT; + + case PAL_VERTEX_TYPE_UINT32_3: + return VK_FORMAT_R32G32B32_UINT; + + case PAL_VERTEX_TYPE_UINT32_4: + return VK_FORMAT_R32G32B32A32_UINT; + + case PAL_VERTEX_TYPE_INT8_2: + return VK_FORMAT_R8G8_SINT; + + case PAL_VERTEX_TYPE_INT8_4: + return VK_FORMAT_R8G8B8A8_SINT; + + case PAL_VERTEX_TYPE_UINT8_2: + return VK_FORMAT_R8G8_UINT; + + case PAL_VERTEX_TYPE_UINT8_4: + return VK_FORMAT_R8G8B8A8_UINT; + + case PAL_VERTEX_TYPE_INT8_2NORM: + return VK_FORMAT_R8G8_SNORM; + + case PAL_VERTEX_TYPE_INT8_4NORM: + return VK_FORMAT_R8G8B8A8_SNORM; + + case PAL_VERTEX_TYPE_UINT8_2NORM: + return VK_FORMAT_R8G8_UNORM; + + case PAL_VERTEX_TYPE_UINT8_4NORM: + return VK_FORMAT_R8G8B8A8_UNORM; + + case PAL_VERTEX_TYPE_INT16_2: + return VK_FORMAT_R16G16_SINT; + + case PAL_VERTEX_TYPE_INT16_4: + return VK_FORMAT_R16G16B16A16_SINT; + + case PAL_VERTEX_TYPE_UINT16_2: + return VK_FORMAT_R16G16_UINT; + + case PAL_VERTEX_TYPE_UINT16_4: + return VK_FORMAT_R16G16B16A16_UINT; + + case PAL_VERTEX_TYPE_INT16_2NORM: + return VK_FORMAT_R16G16_SNORM; + + case PAL_VERTEX_TYPE_INT16_4NORM: + return VK_FORMAT_R16G16B16A16_SNORM; + + case PAL_VERTEX_TYPE_UINT16_2NORM: + return VK_FORMAT_R16G16_UNORM; + + case PAL_VERTEX_TYPE_UINT16_4NORM: + return VK_FORMAT_R16G16B16A16_UNORM; + + case PAL_VERTEX_TYPE_FLOAT: + return VK_FORMAT_R32_SFLOAT; + + case PAL_VERTEX_TYPE_FLOAT2: + return VK_FORMAT_R32G32_SFLOAT; + + case PAL_VERTEX_TYPE_FLOAT3: + return VK_FORMAT_R32G32B32_SFLOAT; + + case PAL_VERTEX_TYPE_FLOAT4: + return VK_FORMAT_R32G32B32A32_SFLOAT; + + case PAL_VERTEX_TYPE_HALF_FLOAT16_2: + return VK_FORMAT_R16G16_SFLOAT; + + case PAL_VERTEX_TYPE_HALF_FLOAT16_4: + return VK_FORMAT_R16G16B16A16_SFLOAT; + } + + return VK_FORMAT_UNDEFINED; +} + +void fillBuildInfoVk( + PalBool getBuildSize, + PalAccelerationStructureBuildInfo* info, + VkAccelerationStructureGeometryKHR* geometries, + VkAccelerationStructureBuildGeometryInfoKHR* outBuildInfo, + void* outData) +{ + for (int i = 0; i < info->count; i++) { + // fill vulkan geometry struct + VkAccelerationStructureBuildRangeInfoKHR* range = nullptr; + uint32_t* maxPrimitives = nullptr; + VkAccelerationStructureGeometryKHR* geometry = &geometries[i]; + + if (getBuildSize) { + maxPrimitives = outData; + } else { + VkAccelerationStructureBuildRangeInfoKHR* tmp = outData; + range = &tmp[i]; + } + + geometry->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geometry->flags = 0; + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL) { + geometry->geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; + VkAccelerationStructureGeometryInstancesDataKHR* data = nullptr; + data = &geometry->geometry.instances; + data->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; + data->arrayOfPointers = PAL_FALSE; + + VkDeviceOrHostAddressConstKHR address = {0}; + address.deviceAddress = info->instanceBufferAddress; + data->data = address; + + if (getBuildSize) { + maxPrimitives[i] = info->count; + + } else { + range->primitiveCount = info->count; + range->firstVertex = 0; // PAL does not allow setting this + range->primitiveOffset = 0; // PAL does not allow setting this + range->transformOffset = 0; // PAL does not allow setting this + } + break; + + } else { + if (info->geometries[i].flags & PAL_GEOMETRY_FLAG_OPAQUE) { + geometry->flags |= VK_GEOMETRY_OPAQUE_BIT_KHR; + } + + if (info->geometries[i].flags & PAL_GEOMETRY_FLAG_NO_DUPLICATE_ANYHIT) { + geometry->flags |= VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR; + } + + if (getBuildSize) { + maxPrimitives[i] = info->geometries[i].primitiveCount; + + } else { + range->primitiveCount = info->geometries[i].primitiveCount; + range->firstVertex = 0; // PAL does not allow setting this + range->primitiveOffset = 0; // PAL does not allow setting this + range->transformOffset = 0; // PAL does not allow setting this + } + } + + if (info->geometries[i].type == PAL_GEOMETRY_TYPE_TRIANGLE) { + geometry->geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + VkAccelerationStructureGeometryTrianglesDataKHR* data = &geometry->geometry.triangles; + data->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + const PalGeometryDataTriangle* tmpData = info->geometries[i].data; + + data->vertexData.deviceAddress = tmpData->vertexBufferAddress; + data->maxVertex = tmpData->vertexCount - 1; + data->vertexFormat = vertexTypeToVk(tmpData->vertexType); + data->vertexStride = tmpData->vertexStride; + + data->transformData.deviceAddress = tmpData->transformBufferAddress; + + data->indexData.deviceAddress = tmpData->indexBufferAddress; + if (tmpData->indexType == PAL_INDEX_TYPE_UINT32) { + data->indexType = VK_INDEX_TYPE_UINT32; + + } else { + data->indexType = VK_INDEX_TYPE_UINT16; + } + + if (!tmpData->indexBufferAddress) { + data->indexType = VK_INDEX_TYPE_NONE_KHR; + } + + } else if (info->geometries[i].type == PAL_GEOMETRY_TYPE_AABBS) { + geometry->geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; + VkAccelerationStructureGeometryAabbsDataKHR* data = &geometry->geometry.aabbs; + data->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR; + + VkDeviceOrHostAddressConstKHR address = {0}; + const PalGeometryDataAABBS* tmpData = info->geometries[i].data; + address.deviceAddress = tmpData->bufferAddress; + data->data = address; + data->stride = tmpData->stride; + } + } + + outBuildInfo->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + if (info->type == PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL) { + outBuildInfo->type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + } else { + outBuildInfo->type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + } + + // build mode + if (info->buildMode == PAL_ACCELERATION_STRUCTURE_BUILD_MODE_BUILD) { + outBuildInfo->mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + } else { + outBuildInfo->mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR; + } + + // build hints + outBuildInfo->flags = 0; + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_BUILD) { + outBuildInfo->flags |= VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR; + } + + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_TRACE) { + outBuildInfo->flags |= VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + } + + if (info->buildHints & PAL_ACCELERATION_STRUCTURE_BUILD_HINT_LOW_MEMORY) { + outBuildInfo->flags |= VK_BUILD_ACCELERATION_STRUCTURE_LOW_MEMORY_BIT_KHR; + } + + AccelerationStructureVk* dstAs = (AccelerationStructureVk*)info->dst; + AccelerationStructureVk* srcAs = (AccelerationStructureVk*)info->src; + VkAccelerationStructureKHR dstAsHandle = nullptr; + if (dstAs) { + dstAsHandle = dstAs->handle; + } + + VkAccelerationStructureKHR srcAsHandle = nullptr; + if (srcAs) { + srcAsHandle = srcAs->handle; + } + + outBuildInfo->geometryCount = info->count; + outBuildInfo->dstAccelerationStructure = dstAsHandle; + outBuildInfo->srcAccelerationStructure = srcAsHandle; + outBuildInfo->pGeometries = geometries; + + VkDeviceOrHostAddressKHR scratchData = {0}; + scratchData.deviceAddress = info->scratchBufferAddress; + outBuildInfo->scratchData = scratchData; +} + +VkPipelineStageFlags2 pipelineStagesToVk(PalPipelineStages stages) +{ + if (stages == PAL_PIPELINE_STAGE_NONE) { + return VK_PIPELINE_STAGE_2_NONE; + } + + VkPipelineStageFlags2 vkStages = 0; + if (stages & PAL_PIPELINE_STAGE_VERTEX_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_FRAGMENT_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_COMPUTE_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_GEOMETRY_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_GEOMETRY_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_TESSELLATION_CONTROL_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_TESSELLATION_EVALUATION_SHADER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_RAY_TRACING_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT_KHR; + } + + if (stages & PAL_PIPELINE_STAGE_TASK_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_TASK_SHADER_BIT_EXT; + } + + if (stages & PAL_PIPELINE_STAGE_MESH_SHADER) { + vkStages |= VK_PIPELINE_STAGE_2_MESH_SHADER_BIT_EXT; + } + + if (stages & PAL_PIPELINE_STAGE_VERTEX_INPUT) { + vkStages |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_INDEX_INPUT) { + vkStages |= VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_EARLY_DEPTH_STENCIL) { + vkStages |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_LATE_DEPTH_STENCIL) { + vkStages |= VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_TRANSFER) { + vkStages |= VK_PIPELINE_STAGE_2_TRANSFER_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_HOST) { + vkStages |= VK_PIPELINE_STAGE_2_HOST_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_COLOR_ATTACHMENT) { + vkStages |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT) { + vkStages |= VK_PIPELINE_STAGE_2_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR; + } + + if (stages & PAL_PIPELINE_STAGE_INDIRECT_INPUT) { + vkStages |= VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT; + } + + if (stages & PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD) { + vkStages |= VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR; + } + + return vkStages; +} + +static void* alignedRealloc( + void* memory, + uint64_t size, + uint64_t alignment) +{ +#if defined(_MSC_VER) || defined(__MINGW32__) + return _aligned_realloc(memory, size, alignment); +#else + return realloc(memory, size); +#endif // _MSC_VER +} + +static void alignedFree(void* ptr) +{ +#if defined(_MSC_VER) || defined(__MINGW32__) + _aligned_free(ptr); +#else + free(ptr); +#endif // _MSC_VER +} + +static void* VKAPI_CALL allocateVk( + void* pUserData, + size_t size, + size_t alignment, + VkSystemAllocationScope allocationScope) +{ + return palAllocate(s_Vk.allocator, size, alignment); +} + +static void VKAPI_CALL freeVk( + void* pUserData, + void* ptr) +{ + palFree(s_Vk.allocator, ptr); +} + +static void* VKAPI_CALL reallocVk( + void* pUserData, + void* pOriginal, + size_t size, + size_t alignment, + VkSystemAllocationScope allocationScope) +{ + // Note: This is a hack which could cost performance but + // realloc is not really called that much so it should be fine + // this is because we dont know the old size + void* block = alignedRealloc(pOriginal, size, alignment); + if (block) { + void* memory = palAllocate(s_Vk.allocator, size, alignment); + if (!memory) { + alignedFree(block); + return nullptr; + } + + memcpy(memory, block, size); + alignedFree(block); + return memory; + } + return nullptr; +} + +static void* loadLibrary(const char* name) +{ +#ifdef _WIN32 + return LoadLibraryA(name); +#elif defined(__linux__) + return dlopen(name, RTLD_LAZY); +#endif +} + +static void freeLibrary(void* lib) +{ +#ifdef _WIN32 + FreeLibrary(lib); +#elif defined(__linux__) + dlclose(lib); +#endif +} + +static void* loadProc( + void* lib, + const char* name) +{ +#ifdef _WIN32 + return GetProcAddress(lib, name); +#elif defined(__linux__) + return dlsym(lib, name); +#endif +} + +static uint32_t getNativeCode() +{ +#ifdef _WIN32 + return GetLastError(); +#elif defined(__linux__) + return errno; +#endif +} + +VkBool32 VKAPI_CALL debugCallbackVk( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagBitsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* userData) +{ + if (!s_Vk.callback) { + return VK_FALSE; + } + + PalDebugMessageSeverity debugSeverity = 0; + PalDebugMessageType debugType = 0; + if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + debugType = PAL_DEBUG_MESSAGE_TYPE_GENERAL; + } + + if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + debugType = PAL_DEBUG_MESSAGE_TYPE_PERFORMANCE; + } + + if (type & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + debugType = PAL_DEBUG_MESSAGE_TYPE_VALIDATION; + } + + if (type & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + debugSeverity = PAL_DEBUG_MESSAGE_SEVERITY_INFO; + } + + if (type & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + debugSeverity = PAL_DEBUG_MESSAGE_SEVERITY_WARNING; + } + + if (type & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + debugSeverity = PAL_DEBUG_MESSAGE_SEVERITY_ERROR; + } + + s_Vk.callback(userData, debugSeverity, debugType, data->pMessage); + return VK_FALSE; +} + +PalResult PAL_CALL initGraphicsVk( + const PalGraphicsDebugger* debugger, + const PalAllocator* allocator) +{ + // load vulkan + s_Vk.handle = loadLibrary(VK_LIB_NAME); + if (!s_Vk.handle) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, RESULT_SOURCE, getNativeCode()); + } + + // clang-format off + s_Vk.enumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion)loadProc( + s_Vk.handle, + "vkEnumerateInstanceVersion"); + + s_Vk.enumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties)loadProc( + s_Vk.handle, + "vkEnumerateInstanceExtensionProperties"); + + s_Vk.createInstance = (PFN_vkCreateInstance)loadProc( + s_Vk.handle, + "vkCreateInstance"); + + s_Vk.destroyInstance = (PFN_vkDestroyInstance)loadProc( + s_Vk.handle, + "vkDestroyInstance"); + + s_Vk.enumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)loadProc( + s_Vk.handle, + "vkEnumeratePhysicalDevices"); + + s_Vk.getPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceProperties"); + + s_Vk.getPhysicalDeviceMemoryProperties = (PFN_vkGetPhysicalDeviceMemoryProperties)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceMemoryProperties"); + + s_Vk.enumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties)loadProc( + s_Vk.handle, + "vkEnumerateInstanceLayerProperties"); + + s_Vk.getPhysicalDeviceQueueFamilyProperties = (PFN_vkGetPhysicalDeviceQueueFamilyProperties)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceQueueFamilyProperties"); + + s_Vk.enumerateDeviceExtensionProperties = (PFN_vkEnumerateDeviceExtensionProperties)loadProc( + s_Vk.handle, + "vkEnumerateDeviceExtensionProperties"); + + s_Vk.getPhysicalDeviceFeatures = (PFN_vkGetPhysicalDeviceFeatures)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceFeatures"); + + s_Vk.getPhysicalDeviceFeatures2 = (PFN_vkGetPhysicalDeviceFeatures2)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceFeatures2"); + + s_Vk.getInstanceProcAddr = (PFN_vkGetInstanceProcAddr)loadProc( + s_Vk.handle, + "vkGetInstanceProcAddr"); + + s_Vk.createImage = (PFN_vkCreateImage)loadProc( + s_Vk.handle, + "vkCreateImage"); + + s_Vk.destroyImage = (PFN_vkDestroyImage)loadProc( + s_Vk.handle, + "vkDestroyImage"); + + s_Vk.createImageView = (PFN_vkCreateImageView)loadProc( + s_Vk.handle, + "vkCreateImageView"); + + s_Vk.destroyImageView = (PFN_vkDestroyImageView)loadProc( + s_Vk.handle, + "vkDestroyImageView"); + + s_Vk.createShader = (PFN_vkCreateShaderModule)loadProc( + s_Vk.handle, + "vkCreateShaderModule"); + + s_Vk.destroyShader = (PFN_vkDestroyShaderModule)loadProc( + s_Vk.handle, + "vkDestroyShaderModule"); + + s_Vk.createSampler = (PFN_vkCreateSampler)loadProc( + s_Vk.handle, + "vkCreateSampler"); + + s_Vk.destroySampler = (PFN_vkDestroySampler)loadProc( + s_Vk.handle, + "vkDestroySampler"); + + s_Vk.getPhysicalDeviceProperties2 = (PFN_vkGetPhysicalDeviceProperties2)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceProperties2"); + + s_Vk.getPhysicalDeviceFormatProperties = (PFN_vkGetPhysicalDeviceFormatProperties)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceFormatProperties"); + + s_Vk.getPhysicalDeviceImageFormatProperties = (PFN_vkGetPhysicalDeviceImageFormatProperties)loadProc( + s_Vk.handle, + "vkGetPhysicalDeviceImageFormatProperties"); + + s_Vk.createDevice = (PFN_vkCreateDevice)loadProc( + s_Vk.handle, + "vkCreateDevice"); + + s_Vk.destroyDevice = (PFN_vkDestroyDevice)loadProc( + s_Vk.handle, + "vkDestroyDevice"); + + s_Vk.getDeviceQueue = (PFN_vkGetDeviceQueue)loadProc( + s_Vk.handle, + "vkGetDeviceQueue"); + + s_Vk.queueSubmit = (PFN_vkQueueSubmit)loadProc( + s_Vk.handle, + "vkQueueSubmit"); + + s_Vk.getDeviceProcAddr = (PFN_vkGetDeviceProcAddr)loadProc( + s_Vk.handle, + "vkGetDeviceProcAddr"); + + s_Vk.getImageMemoryRequirements = (PFN_vkGetImageMemoryRequirements)loadProc( + s_Vk.handle, + "vkGetImageMemoryRequirements"); + + s_Vk.allocateMemory = (PFN_vkAllocateMemory)loadProc( + s_Vk.handle, + "vkAllocateMemory"); + + s_Vk.freeMemory = (PFN_vkFreeMemory)loadProc( + s_Vk.handle, + "vkFreeMemory"); + + s_Vk.bindImageMemory = (PFN_vkBindImageMemory)loadProc( + s_Vk.handle, + "vkBindImageMemory"); + + s_Vk.createCommandPool = (PFN_vkCreateCommandPool)loadProc( + s_Vk.handle, + "vkCreateCommandPool"); + + s_Vk.destroyCommandPool = (PFN_vkDestroyCommandPool)loadProc( + s_Vk.handle, + "vkDestroyCommandPool"); + + s_Vk.allocateCommandBuffer = (PFN_vkAllocateCommandBuffers)loadProc( + s_Vk.handle, + "vkAllocateCommandBuffers"); + + s_Vk.freeCommandBuffer = (PFN_vkFreeCommandBuffers)loadProc( + s_Vk.handle, + "vkFreeCommandBuffers"); + + s_Vk.createFence = (PFN_vkCreateFence)loadProc( + s_Vk.handle, + "vkCreateFence"); + + s_Vk.destroyFence = (PFN_vkDestroyFence)loadProc( + s_Vk.handle, + "vkDestroyFence"); + + s_Vk.resetFence = (PFN_vkResetFences)loadProc( + s_Vk.handle, + "vkResetFences"); + + s_Vk.waitFence = (PFN_vkWaitForFences)loadProc( + s_Vk.handle, + "vkWaitForFences"); + + s_Vk.isFenceSignaled = (PFN_vkGetFenceStatus)loadProc( + s_Vk.handle, + "vkGetFenceStatus"); + + s_Vk.createSemaphore = (PFN_vkCreateSemaphore)loadProc( + s_Vk.handle, + "vkCreateSemaphore"); + + s_Vk.destroySemaphore = (PFN_vkDestroySemaphore)loadProc( + s_Vk.handle, + "vkDestroySemaphore"); + + s_Vk.cmdBegin = (PFN_vkBeginCommandBuffer)loadProc( + s_Vk.handle, + "vkBeginCommandBuffer"); + + s_Vk.cmdEnd = (PFN_vkEndCommandBuffer)loadProc( + s_Vk.handle, + "vkEndCommandBuffer"); + + s_Vk.resetCommandBuffer = (PFN_vkResetCommandBuffer)loadProc( + s_Vk.handle, + "vkResetCommandBuffer"); + + s_Vk.cmdExecuteCommandBuffer = (PFN_vkCmdExecuteCommands)loadProc( + s_Vk.handle, + "vkCmdExecuteCommands"); + + s_Vk.cmdCopyBuffer = (PFN_vkCmdCopyBuffer)loadProc( + s_Vk.handle, + "vkCmdCopyBuffer"); + + s_Vk.cmdCopyBufferToImage = (PFN_vkCmdCopyBufferToImage)loadProc( + s_Vk.handle, + "vkCmdCopyBufferToImage"); + + s_Vk.cmdCopyImage = (PFN_vkCmdCopyImage)loadProc( + s_Vk.handle, + "vkCmdCopyImage"); + + s_Vk.cmdCopyImageToBuffer = (PFN_vkCmdCopyImageToBuffer)loadProc( + s_Vk.handle, + "vkCmdCopyImageToBuffer"); + + s_Vk.cmdBindPipeline = (PFN_vkCmdBindPipeline)loadProc( + s_Vk.handle, + "vkCmdBindPipeline"); + + s_Vk.cmdSetViewports = (PFN_vkCmdSetViewport)loadProc( + s_Vk.handle, + "vkCmdSetViewport"); + + s_Vk.cmdSetScissors = (PFN_vkCmdSetScissor)loadProc( + s_Vk.handle, + "vkCmdSetScissor"); + + s_Vk.cmdBindVertexBuffers = (PFN_vkCmdBindVertexBuffers)loadProc( + s_Vk.handle, + "vkCmdBindVertexBuffers"); + + s_Vk.cmdBindIndexBuffer = (PFN_vkCmdBindIndexBuffer)loadProc( + s_Vk.handle, + "vkCmdBindIndexBuffer"); + + s_Vk.cmdDraw = (PFN_vkCmdDraw)loadProc( + s_Vk.handle, + "vkCmdDraw"); + + s_Vk.cmdDrawIndirect = (PFN_vkCmdDrawIndirect)loadProc( + s_Vk.handle, + "vkCmdDrawIndirect"); + + s_Vk.cmdDrawIndexed = (PFN_vkCmdDrawIndexed)loadProc( + s_Vk.handle, + "vkCmdDrawIndexed"); + + s_Vk.cmdDrawIndexedIndirect = (PFN_vkCmdDrawIndexedIndirect)loadProc( + s_Vk.handle, + "vkCmdDrawIndexedIndirect"); + + s_Vk.cmdDispatch = (PFN_vkCmdDispatch)loadProc( + s_Vk.handle, + "vkCmdDispatch"); + + s_Vk.cmdDispatchIndirect = (PFN_vkCmdDispatchIndirect)loadProc( + s_Vk.handle, + "vkCmdDispatchIndirect"); + + s_Vk.cmdBindDescriptorSets = (PFN_vkCmdBindDescriptorSets)loadProc( + s_Vk.handle, + "vkCmdBindDescriptorSets"); + + s_Vk.cmdPushConstants = (PFN_vkCmdPushConstants)loadProc( + s_Vk.handle, + "vkCmdPushConstants"); + + s_Vk.createBuffer = (PFN_vkCreateBuffer)loadProc( + s_Vk.handle, + "vkCreateBuffer"); + + s_Vk.destroyBuffer = (PFN_vkDestroyBuffer)loadProc( + s_Vk.handle, + "vkDestroyBuffer"); + + s_Vk.mapMemory = (PFN_vkMapMemory)loadProc( + s_Vk.handle, + "vkMapMemory"); + + s_Vk.unmapMemory = (PFN_vkUnmapMemory)loadProc( + s_Vk.handle, + "vkUnmapMemory"); + + s_Vk.getBufferDeviceAddress = (PFN_vkGetBufferDeviceAddress)loadProc( + s_Vk.handle, + "vkGetBufferDeviceAddress"); + + s_Vk.getBufferMemoryRequirements = (PFN_vkGetBufferMemoryRequirements)loadProc( + s_Vk.handle, + "vkGetBufferMemoryRequirements"); + + s_Vk.bindBufferMemory = (PFN_vkBindBufferMemory)loadProc( + s_Vk.handle, + "vkBindBufferMemory"); + + s_Vk.createDescriptorSetLayout = (PFN_vkCreateDescriptorSetLayout)loadProc( + s_Vk.handle, + "vkCreateDescriptorSetLayout"); + + s_Vk.destroyDescriptorSetLayout = (PFN_vkDestroyDescriptorSetLayout)loadProc( + s_Vk.handle, + "vkDestroyDescriptorSetLayout"); + + s_Vk.createDescriptorPool = (PFN_vkCreateDescriptorPool)loadProc( + s_Vk.handle, + "vkCreateDescriptorPool"); + + s_Vk.destroyDescriptorPool = (PFN_vkDestroyDescriptorPool)loadProc( + s_Vk.handle, + "vkDestroyDescriptorPool"); + + s_Vk.resetDescriptorPool = (PFN_vkResetDescriptorPool)loadProc( + s_Vk.handle, + "vkResetDescriptorPool"); + + s_Vk.allocateDescriptorSet = (PFN_vkAllocateDescriptorSets)loadProc( + s_Vk.handle, + "vkAllocateDescriptorSets"); + + s_Vk.updateDescriptorSet = (PFN_vkUpdateDescriptorSets)loadProc( + s_Vk.handle, + "vkUpdateDescriptorSets"); + + s_Vk.createPipelineLayout = (PFN_vkCreatePipelineLayout)loadProc( + s_Vk.handle, + "vkCreatePipelineLayout"); + + s_Vk.destroyPipelineLayout = (PFN_vkDestroyPipelineLayout)loadProc( + s_Vk.handle, + "vkDestroyPipelineLayout"); + + s_Vk.createGraphicsPipeline = (PFN_vkCreateGraphicsPipelines)loadProc( + s_Vk.handle, + "vkCreateGraphicsPipelines"); + + s_Vk.createComputePipeline = (PFN_vkCreateComputePipelines)loadProc( + s_Vk.handle, + "vkCreateComputePipelines"); + + s_Vk.destroyPipeline = (PFN_vkDestroyPipeline)loadProc( + s_Vk.handle, + "vkDestroyPipeline"); + + s_Vk.waitQueue = (PFN_vkQueueWaitIdle)loadProc( + s_Vk.handle, + "vkQueueWaitIdle"); + // clang-format on + + // get version + PalBool versionFallback = PAL_FALSE; + uint32_t version = 0; + if (s_Vk.enumerateInstanceVersion) { + s_Vk.enumerateInstanceVersion(&version); + if (version <= VK_API_VERSION_1_0) { + versionFallback = PAL_TRUE; + } + } + + VkResult result; + uint32_t layerCount = 0; + PalBool hasValidationLayer = PAL_FALSE; + s_Vk.messenger = nullptr; + s_Vk.allocator = allocator; + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {0}; + debugCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + + if (debugger && debugger->callback) { + // layers + result = s_Vk.enumerateInstanceLayerProperties(&layerCount, nullptr); + if (result == VK_SUCCESS) { + VkLayerProperties* props = nullptr; + props = palAllocate(s_Vk.allocator, sizeof(VkLayerProperties) * layerCount, 0); + if (!props) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + s_Vk.enumerateInstanceLayerProperties(&layerCount, props); + for (int i = 0; i < layerCount; i++) { + const char* name = props[i].layerName; + if (strcmp(name, "VK_LAYER_KHRONOS_validation") == 0) { + hasValidationLayer = PAL_TRUE; + break; + } + } + palFree(s_Vk.allocator, props); + + // message types + if (!debugger->denyGeneral) { + debugCreateInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; + } + + if (!debugger->denyPerformance) { + debugCreateInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + } + + if (!debugger->denyValidation) { + debugCreateInfo.messageType |= VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; + } + + // message severities + if (!debugger->denyInfoSeverity) { + debugCreateInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + } + + if (!debugger->denyWarningSeverity) { + debugCreateInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + } + + if (!debugger->denyErrorSeverity) { + debugCreateInfo.messageSeverity |= VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + } + + debugCreateInfo.pUserData = debugger->userData; + debugCreateInfo.pfnUserCallback = debugCallbackVk; + s_Vk.callback = debugger->callback; + } + } + + // extensions + uint32_t extCount = 0; + const char* extensions[8]; + result = s_Vk.enumerateInstanceExtensionProperties(nullptr, &extCount, nullptr); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + VkExtensionProperties* extensionProps = nullptr; + extensionProps = palAllocate(s_Vk.allocator, sizeof(VkExtensionProperties) * extCount, 0); + if (!extensionProps) { + return PAL_RESULT_SUCCESS; + } + + PalBool hasXlib = PAL_FALSE; + PalBool hasXcb = PAL_FALSE; + PalBool hasWayland = PAL_FALSE; + PalBool hasWin32 = PAL_FALSE; + PalBool hasSurface = PAL_FALSE; + PalBool hasExtDebug = PAL_FALSE; + s_Vk.enumerateInstanceExtensionProperties(nullptr, &extCount, extensionProps); + + for (int i = 0; i < extCount; i++) { + VkExtensionProperties* prop = &extensionProps[i]; + if (strcmp(prop->extensionName, "VK_KHR_xlib_surface") == 0) { + hasXlib = PAL_TRUE; + + } else if (strcmp(prop->extensionName, "VK_KHR_xcb_surface") == 0) { + hasXcb = PAL_TRUE; + + } else if (strcmp(prop->extensionName, "VK_KHR_wayland_surface") == 0) { + hasWayland = PAL_TRUE; + + } else if (strcmp(prop->extensionName, "VK_KHR_win32_surface") == 0) { + hasWin32 = PAL_TRUE; + + } else if (strcmp(prop->extensionName, "VK_KHR_surface") == 0) { + hasSurface = PAL_TRUE; + + } else if (strcmp(prop->extensionName, "VK_EXT_debug_utils") == 0) { + hasExtDebug = PAL_TRUE; + } + } + palFree(s_Vk.allocator, extensionProps); + + int extensionCount = 0; + if (hasSurface) { + extensions[extensionCount++] = "VK_KHR_surface"; + if (hasWayland) { + extensions[extensionCount++] = "VK_KHR_wayland_surface"; + } + + if (hasXlib) { + extensions[extensionCount++] = "VK_KHR_xlib_surface"; + } + + if (hasXcb) { + extensions[extensionCount++] = "VK_KHR_xcb_surface"; + } + + if (hasWin32) { + extensions[extensionCount++] = "VK_KHR_win32_surface"; + } + } + + const char* layers[2]; + layerCount = 0; + if (hasValidationLayer && hasExtDebug) { + extensions[extensionCount++] = "VK_EXT_debug_utils"; + layers[layerCount++] = "VK_LAYER_KHRONOS_validation"; + } + + if (versionFallback) { + const char* name = "VK_KHR_get_physical_device_properties2"; + extensions[extensionCount++] = name; + } + + VkApplicationInfo appInfo = {0}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.apiVersion = version; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "Engine"; + appInfo.pApplicationName = "App"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + + VkInstanceCreateInfo instanceCreateInfo = {0}; + instanceCreateInfo.pApplicationInfo = &appInfo; + instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instanceCreateInfo.enabledExtensionCount = extensionCount; + instanceCreateInfo.enabledLayerCount = layerCount; + instanceCreateInfo.ppEnabledExtensionNames = extensions; + instanceCreateInfo.ppEnabledLayerNames = layers; + + if (debugger && debugger->callback) { + instanceCreateInfo.pNext = &debugCreateInfo; + } + + // vk allocator + s_Vk.allocatorImpl.pfnAllocation = allocateVk; + s_Vk.allocatorImpl.pfnFree = freeVk; + s_Vk.allocatorImpl.pfnReallocation = reallocVk; + + VkInstance instance = nullptr; + result = s_Vk.createInstance(&instanceCreateInfo, &s_Vk.allocatorImpl, &instance); + if (result != VK_SUCCESS) { + return makeResultVk(result); + } + + // clang-format off + if (versionFallback) { + // load get physical device properties2 proc if we are on version 1.0 + s_Vk.getPhysicalDeviceFeatures2 = + (PFN_vkGetPhysicalDeviceFeatures2KHR)s_Vk.getInstanceProcAddr( + s_Vk.handle, + "vkGetPhysicalDeviceFeatures2KHR"); + } + + // load surface creation function pointers + s_Vk.createWaylandSurface = nullptr; + s_Vk.createXlibSurface = nullptr; + s_Vk.createXcbSurface = nullptr; + s_Vk.createWin32Surface = nullptr; + + if (hasWayland) { + s_Vk.createWaylandSurface = (PFN_vkCreateWaylandSurfaceKHR)s_Vk.getInstanceProcAddr( + instance, + "vkCreateWaylandSurfaceKHR"); + } + + if (hasXlib) { + s_Vk.createXlibSurface = (PFN_vkCreateXlibSurfaceKHR)s_Vk.getInstanceProcAddr( + instance, + "vkCreateXlibSurfaceKHR"); + } + + if (hasXcb) { + s_Vk.createXcbSurface = (PFN_vkCreateXcbSurfaceKHR)s_Vk.getInstanceProcAddr( + instance, + "vkCreateXcbSurfaceKHR"); + } + + if (hasWin32) { + s_Vk.createWin32Surface = (PFN_vkCreateWin32SurfaceKHR)s_Vk.getInstanceProcAddr( + instance, + "vkCreateWin32SurfaceKHR"); + } + + // remaining function procs + s_Vk.destroySurface = (PFN_vkDestroySurfaceKHR)s_Vk.getInstanceProcAddr( + instance, + "vkDestroySurfaceKHR"); + + s_Vk.getSurfaceCapabilities = + (PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR)s_Vk.getInstanceProcAddr( + instance, + "vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + + s_Vk.getSurfacePresentModes = + (PFN_vkGetPhysicalDeviceSurfacePresentModesKHR)s_Vk.getInstanceProcAddr( + instance, + "vkGetPhysicalDeviceSurfacePresentModesKHR"); + + s_Vk.getSurfaceFormats = (PFN_vkGetPhysicalDeviceSurfaceFormatsKHR)s_Vk.getInstanceProcAddr( + instance, + "vkGetPhysicalDeviceSurfaceFormatsKHR"); + + s_Vk.checkSurfaceSupport = (PFN_vkGetPhysicalDeviceSurfaceSupportKHR)s_Vk.getInstanceProcAddr( + instance, + "vkGetPhysicalDeviceSurfaceSupportKHR"); + + if (debugger) { + s_Vk.createMessenger = + (PFN_vkCreateDebugUtilsMessengerEXT)s_Vk.getInstanceProcAddr( + instance, + "vkCreateDebugUtilsMessengerEXT"); + + s_Vk.destroyMessenger = + (PFN_vkDestroyDebugUtilsMessengerEXT)s_Vk.getInstanceProcAddr( + instance, + "vkDestroyDebugUtilsMessengerEXT"); + + s_Vk.createMessenger(instance, &debugCreateInfo, &s_Vk.allocatorImpl, &s_Vk.messenger); + } + // clang-format on + + s_Vk.adapters = nullptr; + s_Vk.instance = instance; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL shutdownGraphicsVk() +{ + if (s_Vk.messenger) { + s_Vk.destroyMessenger(s_Vk.instance, s_Vk.messenger, &s_Vk.allocatorImpl); + } + + s_Vk.destroyInstance(s_Vk.instance, &s_Vk.allocatorImpl); + freeLibrary(s_Vk.handle); + if (s_Vk.adapters) { + palFree(s_Vk.allocator, s_Vk.adapters); + } + memset(&s_Vk, 0, sizeof(s_Vk)); +} + +#endif // PAL_HAS_VULKAN_BACKEND \ No newline at end of file diff --git a/src/graphics/vulkan/pal_vulkan.h b/src/graphics/vulkan/pal_vulkan.h new file mode 100644 index 00000000..79331c63 --- /dev/null +++ b/src/graphics/vulkan/pal_vulkan.h @@ -0,0 +1,490 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_VULKAN_H +#define _PAL_VULKAN_H + +#if PAL_HAS_VULKAN_BACKEND +#include "graphics/pal_linear_allocator.h" +#include "pal2/pal_graphics.h" +#include + +typedef struct _XDisplay Display; +typedef unsigned long Window; +typedef struct xcb_connection_t xcb_connection_t; +typedef uint32_t xcb_window_t; +typedef struct HINSTANCE__* HINSTANCE; +typedef struct HWND__* HWND; + +typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; +typedef VkFlags VkXlibSurfaceCreateFlagsKHR; +typedef VkFlags VkXcbSurfaceCreateFlagsKHR; +typedef VkFlags VkWin32SurfaceCreateFlagsKHR; + +typedef struct VkWaylandSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkWaylandSurfaceCreateFlagsKHR flags; + struct wl_display* display; + struct wl_surface* surface; +} VkWaylandSurfaceCreateInfoKHR; + +typedef struct VkXlibSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkXlibSurfaceCreateFlagsKHR flags; + Display* dpy; + Window window; +} VkXlibSurfaceCreateInfoKHR; + +typedef struct VkXcbSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkXcbSurfaceCreateFlagsKHR flags; + xcb_connection_t* connection; + xcb_window_t window; +} VkXcbSurfaceCreateInfoKHR; + +typedef struct VkWin32SurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkWin32SurfaceCreateFlagsKHR flags; + HINSTANCE hinstance; + HWND hwnd; +} VkWin32SurfaceCreateInfoKHR; + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)( + VkInstance, + const VkWaylandSurfaceCreateInfoKHR*, + const VkAllocationCallbacks*, + VkSurfaceKHR*); + +typedef VkResult(VKAPI_PTR* PFN_vkCreateXlibSurfaceKHR)( + VkInstance, + const VkXlibSurfaceCreateInfoKHR*, + const VkAllocationCallbacks*, + VkSurfaceKHR*); + +typedef VkResult(VKAPI_PTR* PFN_vkCreateXcbSurfaceKHR)( + VkInstance, + const VkXcbSurfaceCreateInfoKHR*, + const VkAllocationCallbacks*, + VkSurfaceKHR*); + +typedef VkResult(VKAPI_PTR* PFN_vkCreateWin32SurfaceKHR)( + VkInstance, + const VkWin32SurfaceCreateInfoKHR*, + const VkAllocationCallbacks*, + VkSurfaceKHR*); + +typedef struct { + int32_t familyIndex; + VkPhysicalDevice phyDevice; + VkQueue handle; + VkQueueFlags usages; + VkQueueFlags usedUsages; +} PhysicalQueue; + +typedef struct { + uint32_t maxPayloadSize; +} DeviceLimits; + +typedef struct { + uint32_t patchControlPoints; + VkShaderStageFlagBits stage; + char entryName[PAL_SHADER_ENTRY_NAME_SIZE]; +} ShaderEntry; + +typedef struct { + uint32_t raygenCount; + uint32_t raygenDataSize; + uint32_t missCount; + uint32_t missDataSize; + uint32_t hitCount; + uint32_t hitDataSize; + uint32_t callableCount; + uint32_t callableDataSize; +} ShaderBindingTableInfo; + +typedef struct { + uint32_t startIndex; + uint32_t offset; + VkStridedDeviceAddressRegionKHR region; +} AddressRegion; + +typedef struct { + void* reserved; + VkPhysicalDevice handle; +} AdapterVk; + +typedef struct { + void* reserved; + int32_t phyQueueCount; + int32_t phyQueueIndex; + int32_t queueFamilyCount; + PalAdapterFeatures features; + uint32_t memoryClassMask[3]; + VkPhysicalDevice phyDevice; + VkDevice handle; + PhysicalQueue* phyQueues; + PFN_vkGetBufferDeviceAddress getBufferrAddress; + VkShaderStageFlags shaderStages; + PFN_vkCmdDispatchBase cmdDispatchBase; + + // draw indirect + PFN_vkCmdDrawIndirectCount cmdDrawIndirectCount; + PFN_vkCmdDrawIndexedIndirectCount cmdDrawIndexedIndirectCount; + + // swapchain + PFN_vkCreateSwapchainKHR createSwapchain; + PFN_vkDestroySwapchainKHR destroySwapchain; + PFN_vkGetSwapchainImagesKHR getSwapchainImages; + PFN_vkAcquireNextImageKHR acquireNextImage; + PFN_vkQueuePresentKHR queuePresent; + + // semaphore + PFN_vkWaitSemaphores waitSemaphore; + PFN_vkSignalSemaphore signalSemaphore; + PFN_vkGetSemaphoreCounterValue getSemaphoreValue; + + // fragment shading rate + PFN_vkCmdSetFragmentShadingRateKHR cmdSetFragmentShadingRate; + + // mesh shader + PFN_vkCmdDrawMeshTasksEXT cmdDrawMeshTask; + PFN_vkCmdDrawMeshTasksIndirectEXT cmdDrawMeshTaskIndirect; + PFN_vkCmdDrawMeshTasksIndirectCountEXT cmdDrawMeshTaskIndirectCount; + + // ray tracing + PFN_vkCreateAccelerationStructureKHR createAccelerationStructure; + PFN_vkDestroyAccelerationStructureKHR destroyAccelerationStructure; + PFN_vkGetAccelerationStructureBuildSizesKHR getAccelerationBuildsize; + PFN_vkCmdBuildAccelerationStructuresKHR cmdBuildAccelerationStructures; + PFN_vkGetAccelerationStructureDeviceAddressKHR getAccelerationDeviceAddress; + PFN_vkCmdTraceRaysKHR cmdTraceRays; + PFN_vkCreateRayTracingPipelinesKHR createRayTracingPipeline; + PFN_vkCmdTraceRaysIndirectKHR cmdTraceRaysIndirect; + PFN_vkGetRayTracingShaderGroupHandlesKHR getRayTracingShaderGroupHandles; + + // dynamic rendering + PFN_vkCmdBeginRendering cmdBeginRendering; + PFN_vkCmdEndRendering cmdEndRendering; + PFN_vkCmdPipelineBarrier2 cmdPipelineBarrier; + PFN_vkQueueSubmit2 queueSubmit; + + // dynamic states + PFN_vkCmdSetCullMode cmdSetCullMode; + PFN_vkCmdSetFrontFace cmdSetFrontFace; + PFN_vkCmdSetPrimitiveTopology cmdSetPrimitiveTopology; + PFN_vkCmdSetDepthTestEnable cmdSetDepthTestEnable; + PFN_vkCmdSetDepthWriteEnable cmdSetDepthWriteEnable; + PFN_vkCmdSetStencilOp cmdSetStencilOp; + + DeviceLimits limits; +} DeviceVk; + +typedef struct { + void* reserved; + VkQueueFlags usage; + DeviceVk* device; + PhysicalQueue* phyQueue; +} QueueVk; + +typedef struct { + void* reserved; + DeviceVk* device; + PalMemoryType type; + VkDeviceMemory handle; +} MemoryVk; + +typedef struct { + void* reserved; + PalBool isMemoryManaged; + DeviceVk* device; + MemoryVk* memory; + VkImage handle; + PalImageInfo info; +} ImageVk; + +typedef struct { + void* reserved; + uint32_t layerCount; + DeviceVk* device; + ImageVk* image; + VkImageView handle; +} ImageViewVk; + +typedef struct { + void* reserved; + DeviceVk* device; + VkSurfaceKHR handle; +} SurfaceVk; + +typedef struct { + void* reserved; + uint32_t imageCount; + DeviceVk* device; + QueueVk* queue; + VkSwapchainKHR handle; + ImageVk* images; +} SwapchainVk; + +typedef struct { + void* reserved; + uint32_t entryCount; + ShaderEntry* entries; + DeviceVk* device; + VkShaderModule handle; +} ShaderVk; + +typedef struct { + void* reserved; + DeviceVk* device; + VkCommandPool handle; +} CommandPoolVk; + +typedef struct { + void* reserved; + PalBool primary; + DeviceVk* device; + CommandPoolVk* pool; + void* pipeline; + VkBuffer buffer; + VkDeviceMemory bufferMemory; + VkCommandBuffer handle; + PalLinearAllocator allocator; +} CommandBufferVk; + +typedef struct { + void* reserved; + DeviceVk* device; + VkFence handle; +} FenceVk; + +typedef struct { + void* reserved; + PalBool isTimeline; + DeviceVk* device; + VkSemaphore handle; +} SemaphoreVk; + +typedef struct { + void* reserved; + PalBool isMemoryManaged; + PalBufferUsages usages; + MemoryVk* memory; + DeviceVk* device; + VkBuffer handle; +} BufferVk; + +typedef struct { + void* reserved; + VkDeviceAddress address; + VkBuffer buffer; + VkDeviceMemory bufferMemory; + VkDeviceAddress bufferAddress; + DeviceVk* device; + VkAccelerationStructureKHR handle; +} AccelerationStructureVk; + +typedef struct { + void* reserved; + PalDescriptorIndexingFlags flags; + DeviceVk* device; + VkDescriptorSetLayout handle; +} DescriptorSetLayoutVk; + +typedef struct { + void* reserved; + PalDescriptorIndexingFlags flags; + DeviceVk* device; + VkDescriptorPool handle; +} DescriptorPoolVk; + +typedef struct { + void* reserved; + DeviceVk* device; + DescriptorPoolVk* pool; + VkDescriptorSet handle; +} DescriptorSetVk; + +typedef struct { + void* reserved; + DeviceVk* device; + VkSampler handle; +} SamplerVk; + +typedef struct { + void* reserved; + DeviceVk* device; + VkPipelineLayout handle; +} PipelineLayoutVk; + +typedef struct { + void* reserved; + VkPipelineBindPoint bindPoint; + DeviceVk* device; + VkPipeline handle; + VkPipelineLayout layout; + ShaderBindingTableInfo sbtInfo; +} PipelineVk; + +typedef struct { + void* reserved; + void* stagingPtr; + PalBool isDirty; + uint32_t stagingBufferSize; + uint32_t handleSize; + DeviceVk* device; + VkBuffer buffer; + VkBuffer stagingBuffer; + VkDeviceMemory bufferMemory; + VkDeviceMemory stagingBufferMemory; + VkDeviceAddress baseAddress; + PipelineVk* pipeline; + AddressRegion raygen; + AddressRegion miss; + AddressRegion hit; + AddressRegion callable; +} ShaderBindingTableVk; + +typedef struct { + PFN_vkEnumerateInstanceVersion enumerateInstanceVersion; + PFN_vkEnumerateInstanceExtensionProperties enumerateInstanceExtensionProperties; + PFN_vkDestroyInstance destroyInstance; + PFN_vkCreateInstance createInstance; + PFN_vkEnumeratePhysicalDevices enumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties getPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceMemoryProperties getPhysicalDeviceMemoryProperties; + PFN_vkEnumerateInstanceLayerProperties enumerateInstanceLayerProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties getPhysicalDeviceQueueFamilyProperties; + PFN_vkEnumerateDeviceExtensionProperties enumerateDeviceExtensionProperties; + PFN_vkGetPhysicalDeviceFeatures getPhysicalDeviceFeatures; + PFN_vkGetPhysicalDeviceFeatures2 getPhysicalDeviceFeatures2; + PFN_vkGetInstanceProcAddr getInstanceProcAddr; + PFN_vkCreateImageView createImageView; + PFN_vkDestroyImageView destroyImageView; + PFN_vkGetPhysicalDeviceProperties2 getPhysicalDeviceProperties2; + PFN_vkGetPhysicalDeviceFormatProperties getPhysicalDeviceFormatProperties; + PFN_vkGetPhysicalDeviceImageFormatProperties getPhysicalDeviceImageFormatProperties; + PFN_vkGetImageMemoryRequirements getImageMemoryRequirements; + PFN_vkAllocateMemory allocateMemory; + PFN_vkFreeMemory freeMemory; + PFN_vkBindImageMemory bindImageMemory; + PFN_vkCreateDevice createDevice; + PFN_vkDestroyDevice destroyDevice; + PFN_vkGetDeviceQueue getDeviceQueue; + PFN_vkQueueSubmit queueSubmit; + PFN_vkGetDeviceProcAddr getDeviceProcAddr; + PFN_vkCreateImage createImage; + PFN_vkDestroyImage destroyImage; + PFN_vkCreateShaderModule createShader; + PFN_vkDestroyShaderModule destroyShader; + PFN_vkCreateSampler createSampler; + PFN_vkDestroySampler destroySampler; + PFN_vkCreateCommandPool createCommandPool; + PFN_vkDestroyCommandPool destroyCommandPool; + PFN_vkAllocateCommandBuffers allocateCommandBuffer; + PFN_vkFreeCommandBuffers freeCommandBuffer; + PFN_vkCreateFence createFence; + PFN_vkDestroyFence destroyFence; + PFN_vkResetFences resetFence; + PFN_vkWaitForFences waitFence; + PFN_vkGetFenceStatus isFenceSignaled; + PFN_vkCreateSemaphore createSemaphore; + PFN_vkDestroySemaphore destroySemaphore; + PFN_vkBeginCommandBuffer cmdBegin; + PFN_vkEndCommandBuffer cmdEnd; + PFN_vkResetCommandBuffer resetCommandBuffer; + PFN_vkCmdExecuteCommands cmdExecuteCommandBuffer; + PFN_vkCmdCopyBuffer cmdCopyBuffer; + PFN_vkCmdCopyBufferToImage cmdCopyBufferToImage; + PFN_vkCmdCopyImage cmdCopyImage; + PFN_vkCmdCopyImageToBuffer cmdCopyImageToBuffer; + PFN_vkCmdBindPipeline cmdBindPipeline; + PFN_vkCmdSetViewport cmdSetViewports; + PFN_vkCmdSetScissor cmdSetScissors; + PFN_vkCmdBindVertexBuffers cmdBindVertexBuffers; + PFN_vkCmdBindIndexBuffer cmdBindIndexBuffer; + PFN_vkCmdDraw cmdDraw; + PFN_vkCmdDrawIndirect cmdDrawIndirect; + PFN_vkCmdDrawIndexed cmdDrawIndexed; + PFN_vkCmdDrawIndexedIndirect cmdDrawIndexedIndirect; + PFN_vkCmdDispatch cmdDispatch; + PFN_vkCmdDispatchIndirect cmdDispatchIndirect; + PFN_vkCmdBindDescriptorSets cmdBindDescriptorSets; + PFN_vkCmdPushConstants cmdPushConstants; + PFN_vkCreateWaylandSurfaceKHR createWaylandSurface; + PFN_vkCreateXlibSurfaceKHR createXlibSurface; + PFN_vkCreateXcbSurfaceKHR createXcbSurface; + PFN_vkCreateWin32SurfaceKHR createWin32Surface; + PFN_vkDestroySurfaceKHR destroySurface; + PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR getSurfaceCapabilities; + PFN_vkGetPhysicalDeviceSurfaceFormatsKHR getSurfaceFormats; + PFN_vkGetPhysicalDeviceSurfacePresentModesKHR getSurfacePresentModes; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR checkSurfaceSupport; + PFN_vkCreateBuffer createBuffer; + PFN_vkDestroyBuffer destroyBuffer; + PFN_vkGetBufferMemoryRequirements getBufferMemoryRequirements; + PFN_vkBindBufferMemory bindBufferMemory; + PFN_vkMapMemory mapMemory; + PFN_vkUnmapMemory unmapMemory; + PFN_vkGetBufferDeviceAddress getBufferDeviceAddress; + PFN_vkCreateDescriptorSetLayout createDescriptorSetLayout; + PFN_vkDestroyDescriptorSetLayout destroyDescriptorSetLayout; + PFN_vkCreateDescriptorPool createDescriptorPool; + PFN_vkDestroyDescriptorPool destroyDescriptorPool; + PFN_vkResetDescriptorPool resetDescriptorPool; + PFN_vkAllocateDescriptorSets allocateDescriptorSet; + PFN_vkUpdateDescriptorSets updateDescriptorSet; + PFN_vkCreatePipelineLayout createPipelineLayout; + PFN_vkDestroyPipelineLayout destroyPipelineLayout; + PFN_vkCreateGraphicsPipelines createGraphicsPipeline; + PFN_vkCreateComputePipelines createComputePipeline; + PFN_vkDestroyPipeline destroyPipeline; + PFN_vkCreateDebugUtilsMessengerEXT createMessenger; + PFN_vkDestroyDebugUtilsMessengerEXT destroyMessenger; + PFN_vkQueueWaitIdle waitQueue; + + void* handle; + VkInstance instance; + VkDebugUtilsMessengerEXT messenger; + PalDebugCallback callback; + const PalAllocator* allocator; + AdapterVk* adapters; + VkAllocationCallbacks allocatorImpl; +} Vulkan; + +extern Vulkan s_Vk; + +PalResult makeResultVk(VkResult result); +VkFormat formatToVk(PalFormat format); +VkSampleCountFlags samplesToVk(PalSampleCount count); +VkExtent2D getShadingRateSizeVk(PalFragmentShadingRate rate); + +VkFragmentShadingRateCombinerOpKHR combinerOpsToVk(PalFragmentShadingRateCombinerOp op); +VkImageAspectFlags imageAspectToVk(PalImageAspect aspect); +VkStencilOp stencilOpToVk(PalStencilOp op); +VkCompareOp compareOpToVk(PalCompareOp op); + +uint32_t findBestMemoryIndexVk( + VkPhysicalDevice phyDevice, + uint32_t memoryMask); + +VkFormat vertexTypeToVk(PalVertexType type); + +void fillBuildInfoVk( + PalBool getBuildSize, + PalAccelerationStructureBuildInfo* info, + VkAccelerationStructureGeometryKHR* geometries, + VkAccelerationStructureBuildGeometryInfoKHR* outBuildInfo, + void* outData); + +VkPipelineStageFlags2 pipelineStagesToVk(PalPipelineStages stages); + +#endif // PAL_HAS_VULKAN_BACKEND +#endif // _PAL_VULKAN_H \ No newline at end of file diff --git a/src/opengl/egl/pal_context_egl.c b/src/opengl/egl/pal_context_egl.c new file mode 100644 index 00000000..5738d5c4 --- /dev/null +++ b/src/opengl/egl/pal_context_egl.c @@ -0,0 +1,345 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_EGL +#include "pal_egl.h" + +static ContextData* getFreeContextData() +{ + for (int i = 0; i < s_Egl.maxContextData; ++i) { + if (!s_Egl.contextData[i].used) { + s_Egl.contextData[i].used = PAL_TRUE; + return &s_Egl.contextData[i]; + } + } + + // resize the data array + // this will almost not reach here since most setups are 1-4 monitors + ContextData* data = nullptr; + int count = s_Egl.maxContextData * 2; // double the size + int freeIndex = s_Egl.maxContextData + 1; + data = palAllocate(s_Egl.allocator, sizeof(ContextData) * count, 0); + if (data) { + memcpy(data, s_Egl.contextData, s_Egl.maxContextData * sizeof(ContextData)); + + palFree(s_Egl.allocator, s_Egl.contextData); + s_Egl.contextData = data; + s_Egl.maxContextData = count; + + s_Egl.contextData[freeIndex].used = PAL_TRUE; + return &s_Egl.contextData[freeIndex]; + } + return nullptr; +} + +static ContextData* findContextData(PalGLContext* context) +{ + for (int i = 0; i < s_Egl.maxContextData; ++i) { + if (s_Egl.contextData[i].used && s_Egl.contextData[i].context == context) { + return &s_Egl.contextData[i]; + } + } + return nullptr; +} + +static void freeContextData(PalGLContext* context) +{ + for (int i = 0; i < s_Egl.maxContextData; ++i) { + if (s_Egl.contextData[i].used && s_Egl.contextData[i].context == context) { + s_Egl.contextData[i].used = PAL_FALSE; + } + } +} + +PalResult eglCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext) +{ + if (!info || !outContext) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (!info->window || !info->fbConfig) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + // check if the window was created with the same instance + // the opengl system is using + if (info->window->instance != s_Egl.instance) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // check support for requested features + if (info->profile != PAL_GL_PROFILE_NONE) { + if (!(s_Egl.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->forward) { + if (!(s_Egl.info.extensions & PAL_GL_EXTENSION_CREATE_CONTEXT)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + if (!(s_Egl.info.extensions & PAL_GL_EXTENSION_ROBUSTNESS)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->noError) { + if (!(s_Egl.info.extensions & PAL_GL_EXTENSION_NO_ERROR)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + if (!(s_Egl.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + // check version + // clang-format off + PalBool valid = info->major < s_Egl.info.major || + (info->major == s_Egl.info.major && info->minor <= s_Egl.info.minor); + // clang-format on + + if (!valid) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + ContextData* data = getFreeContextData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // we need to get the EGL config from the user supplied index + EGLint numConfigs = 0; + if (!s_Egl.getConfigs(s_Egl.display, nullptr, 0, &numConfigs)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_EGL, + s_Egl.getError()); + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_Egl.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + s_Egl.getConfigs(s_Egl.display, eglConfigs, numConfigs, &numConfigs); + EGLConfig config = eglConfigs[info->fbConfig->index]; + EGLContext* share = nullptr; + if (info->shareContext) { + share = (EGLContext)info->shareContext; + } else { + share = EGL_NO_CONTEXT; + } + + int32_t attribs[40]; + int32_t index = 0; + int32_t profile = 0; + int32_t flags = 0; + + // set context attributes + // the first element is the key and the second is the value + if (s_Egl.apiType == EGL_OPENGL_API) { + // set version + attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR; // key + attribs[index++] = info->major; // value + + attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR; + attribs[index++] = info->minor; + + // set profile mask + if (info->profile != PAL_GL_PROFILE_NONE) { + attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + + if (info->profile == PAL_GL_PROFILE_COMPATIBILITY) { + profile = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + } else if (info->profile == PAL_GL_PROFILE_CORE) { + profile = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR; + } + + attribs[index++] = info->profile; + } + + // set forward flag + if (info->forward) { + flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; + } + + } else { + attribs[index++] = EGL_CONTEXT_CLIENT_VERSION; + // our configs support EGL_OPENGL_ES2_BIT and EGL_OPENGL_ES3_BIT + attribs[index++] = info->major; + } + + // set debug flag + if (info->debug) { + flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; + } + + // set robustness + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; + attribs[index++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR; + + if (info->reset == PAL_GL_CONTEXT_RESET_LOSE_CONTEXT) { + attribs[index++] = EGL_LOSE_CONTEXT_ON_RESET_KHR; + + } else if (info->reset == PAL_GL_CONTEXT_RESET_NO_NOTIFICATION) { + attribs[index++] = EGL_NO_RESET_NOTIFICATION_KHR; + } + } + + // set no error + if (info->noError) { + attribs[index++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; + attribs[index++] = PAL_TRUE; + } + + // release + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_KHR; + attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR; + } + + if (flags) { + attribs[index++] = EGL_CONTEXT_FLAGS_KHR; + attribs[index++] = flags; + } + attribs[index++] = EGL_NONE; + + // create context + EGLContext context = s_Egl.createContext(s_Egl.display, config, share, attribs); + if (context == EGL_NO_CONTEXT) { + EGLint error = s_Egl.getError(); + if (error == EGL_BAD_CONFIG || error == EGL_BAD_ATTRIBUTE) { + return palMakeResult(PAL_RESULT_CODE_INVALID_ARGUMENT, PAL_RESULT_SOURCE_EGL, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + } + + // create a window surface + // The only attrib we want is colorspace + EGLint surfaceAttribs[3]; + surfaceAttribs[0] = EGL_GL_COLORSPACE; + if (info->fbConfig->sRGB) { + surfaceAttribs[1] = EGL_GL_COLORSPACE_SRGB_KHR; + } else { + surfaceAttribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; + } + surfaceAttribs[2] = EGL_NONE; + + EGLSurface surface = s_Egl.createWindowSurface( + s_Egl.display, + config, + (EGLNativeWindowType)info->window->window, + surfaceAttribs); + + if (surface == EGL_NO_SURFACE) { + EGLint error = s_Egl.getError(); + if (error == EGL_BAD_CONFIG) { + return palMakeResult(PAL_RESULT_CODE_INVALID_ARGUMENT, PAL_RESULT_SOURCE_EGL, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + } + + // make the context current and swap for the first time + // this will make the window visible + if (s_Egl.makeCurrent(s_Egl.display, surface, surface, context)) { + s_Egl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + s_Egl.glClear(0x00004000); + s_Egl.swapBuffers(s_Egl.display, surface); + + // revert + s_Egl.makeCurrent(s_Egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + + palFree(s_Egl.allocator, eglConfigs); + data->context = (PalGLContext*)context; + data->surface = surface; + + *outContext = (PalGLContext*)context; + return PAL_RESULT_SUCCESS; +} + +void eglDestroyGLContext(PalGLContext* context) +{ + ContextData* data = findContextData(context); + // make it not current if it was current + s_Egl.makeCurrent(s_Egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + s_Egl.destroyContext(s_Egl.display, (EGLContext)context); + s_Egl.destroySurface(s_Egl.display, data->surface); + data->used = PAL_FALSE; +} + +PalResult eglMakeContextCurrent( + PalGLWindow* glWindow, + PalGLContext* context) +{ + if (context && glWindow) { + ContextData* data = findContextData(context); + if (!data) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + EGLint ret; + ret = s_Egl.makeCurrent(s_Egl.display, data->surface, data->surface, (EGLConfig)context); + if (!ret) { + EGLint error = s_Egl.getError(); + if (error == EGL_BAD_CONTEXT || error == EGL_BAD_SURFACE) { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_EGL, error); + + } else { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_EGL, + error); + } + } + + } else if (!context && !glWindow) { + s_Egl.makeCurrent(s_Egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult eglSwapBuffers( + PalGLWindow* glWindow, + PalGLContext* context) +{ + ContextData* data = findContextData(context); + if (!data) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + if (!s_Egl.swapBuffers(s_Egl.display, data->surface)) { + EGLint error = s_Egl.getError(); + if (error == EGL_BAD_CONTEXT || error == EGL_BAD_SURFACE) { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_EGL, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + } + + return PAL_RESULT_SUCCESS; +} + +#endif // _PAL_HAS_EGL \ No newline at end of file diff --git a/src/opengl/egl/pal_egl.c b/src/opengl/egl/pal_egl.c new file mode 100644 index 00000000..ef48c003 --- /dev/null +++ b/src/opengl/egl/pal_egl.c @@ -0,0 +1,477 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_EGL +#include "opengl/pal_opengl_shared.h" +#include "pal_egl.h" +#include +#include +#include + +EGL s_Egl = {0}; +static PalBool s_SupportedAPIs[2] = {0}; + +PalResult PAL_CALL eglInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator) +{ + s_Egl.maxContextData = 16; // initial size + s_Egl.contextData = palAllocate(s_Egl.allocator, sizeof(ContextData) * s_Egl.maxContextData, 0); + + if (!s_Egl.maxContextData) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + s_Egl.handle = dlopen("libEGL.so", RTLD_LAZY); + if (!s_Egl.handle) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + // clang-format off + s_Egl.getProcAddress = (eglGetProcAddressFn)dlsym( + s_Egl.handle, + "eglGetProcAddress"); + + s_Egl.createPbufferSurface = (eglCreatePbufferSurfaceFn)s_Egl.getProcAddress( + "eglCreatePbufferSurface"); + + s_Egl.createWindowSurface = (eglCreateWindowSurfaceFn)s_Egl.getProcAddress( + "eglCreateWindowSurface"); + + s_Egl.createContext = (eglCreateContextFn)s_Egl.getProcAddress("eglCreateContext"); + s_Egl.destroyContext = (eglDestroyContextFn)s_Egl.getProcAddress("eglDestroyContext"); + s_Egl.destroySurface = (eglDestroySurfaceFn)s_Egl.getProcAddress("eglDestroySurface"); + s_Egl.makeCurrent = (eglMakeCurrentFn)s_Egl.getProcAddress("eglMakeCurrent"); + s_Egl.swapBuffers = (eglSwapBuffersFn)s_Egl.getProcAddress("eglSwapBuffers"); + s_Egl.swapInterval = (eglSwapIntervalFn)s_Egl.getProcAddress("eglSwapInterval"); + s_Egl.initialize = (eglInitializeFn)s_Egl.getProcAddress("eglInitialize"); + s_Egl.terminate = (eglTerminateFn)s_Egl.getProcAddress("eglTerminate"); + s_Egl.getDisplay = (eglGetDisplayFn)s_Egl.getProcAddress("eglGetDisplay"); + s_Egl.chooseConfig = (eglChooseConfigFn)s_Egl.getProcAddress("eglChooseConfig"); + + s_Egl.getConfigAttrib = (eglGetConfigAttribFn)s_Egl.getProcAddress("eglGetConfigAttrib"); + s_Egl.getError = (eglGetErrorFn)s_Egl.getProcAddress("eglGetError"); + s_Egl.bindAPI = (eglBindAPIFn)s_Egl.getProcAddress("eglBindAPI"); + s_Egl.queryString = (eglQueryStringFn)s_Egl.getProcAddress("eglQueryString"); + s_Egl.getConfigs = (eglGetConfigsFn)s_Egl.getProcAddress("eglGetConfigs"); + s_Egl.glClearColor = (glClearColorFn)s_Egl.getProcAddress("glClearColor"); + s_Egl.glClear = (glClearFn)s_Egl.getProcAddress("glClear"); + + if (!s_Egl.bindAPI || + !s_Egl.chooseConfig || + !s_Egl.createContext || + !s_Egl.createPbufferSurface || + !s_Egl.destroyContext || + !s_Egl.destroySurface || + !s_Egl.getConfigAttrib || + !s_Egl.getDisplay || + !s_Egl.getError || + !s_Egl.getProcAddress || + !s_Egl.initialize || + !s_Egl.makeCurrent || + !s_Egl.swapBuffers || + !s_Egl.swapInterval || + !s_Egl.terminate || + !s_Egl.queryString || + !s_Egl.getConfigs || + !s_Egl.createWindowSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + // clang-format on + + // get api type + if (api == PAL_GL_API_OPENGL) { + s_Egl.apiType = EGL_OPENGL_API; + s_Egl.apiTypeBit = EGL_OPENGL_BIT; + + } else { + s_Egl.apiType = EGL_OPENGL_ES_API; + s_Egl.apiTypeBit = EGL_OPENGL_ES2_BIT; // default + } + + EGLint error = 0; + if (!s_Egl.bindAPI(s_Egl.apiType)) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + EGLDisplay display = s_Egl.getDisplay(instance); + EGLDisplay* tmpDisplay = EGL_NO_DISPLAY; + if (display == EGL_NO_DISPLAY) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + if (!s_Egl.initialize(display, nullptr, nullptr)) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + // use a simple FBConfig + EGLConfig config; + int numConfigs; + EGLint type; + + // clang-format off + EGLint attribs[] = { + EGL_RENDERABLE_TYPE, + s_Egl.apiTypeBit, + EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_NONE + }; + // clang-format on + + s_Egl.chooseConfig(display, attribs, &config, 1, &numConfigs); + if (!config || numConfigs == 0) { + // API bind type might not support puffer + // create a default display and use that + tmpDisplay = s_Egl.getDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + if (!s_Egl.initialize(tmpDisplay, nullptr, nullptr)) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + } else { + // set the tmp display to our display + tmpDisplay = display; + } + + s_Egl.chooseConfig(tmpDisplay, attribs, &config, 1, &numConfigs); + if (!config) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + s_Egl.getConfigAttrib(tmpDisplay, config, EGL_RENDERABLE_TYPE, &type); + if (!(type & s_Egl.apiTypeBit)) { + // we must support the required API + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + EGLSurface surface = EGL_NO_SURFACE; + EGLint pBufferAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; + surface = s_Egl.createPbufferSurface(tmpDisplay, config, pBufferAttribs); + if (surface == EGL_NO_SURFACE) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + // create a dummy context + EGLContext context = EGL_NO_CONTEXT; + if (s_Egl.apiType == EGL_OPENGL_API) { + // clang-format off + EGLint contextAttrib[] = { + EGL_CONTEXT_MAJOR_VERSION, + 2, + EGL_CONTEXT_MINOR_VERSION, + 1, + EGL_NONE + }; + // clang-format on + context = s_Egl.createContext(tmpDisplay, config, EGL_NO_CONTEXT, contextAttrib); + + } else { + EGLint contextAttrib[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + context = s_Egl.createContext(tmpDisplay, config, EGL_NO_CONTEXT, contextAttrib); + } + + if (context == EGL_NO_CONTEXT) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + s_Egl.makeCurrent(tmpDisplay, surface, surface, context); + s_Egl.glGetString = (glGetStringFn)s_Egl.getProcAddress("glGetString"); + const char* version = (const char*)s_Egl.glGetString(GL_VERSION); + if (version) { + if (s_Egl.apiType == EGL_OPENGL_API) { + sscanf(version, "%d.%d", &s_Egl.info.major, &s_Egl.info.minor); + } else { + sscanf(version + 10, "%d.%d", &s_Egl.info.major, &s_Egl.info.minor); + } + } + + const char* renderer = (const char*)s_Egl.glGetString(GL_RENDERER); + const char* vendor = (const char*)s_Egl.glGetString(GL_VENDOR); + strcpy(s_Egl.info.vendor, vendor); + strcpy(s_Egl.info.version, version); + strcpy(s_Egl.info.graphicsCard, renderer); + + // EGL extensions can be queried without a bound context + // we just do that over here after making the context current + const char* extensions = s_Egl.queryString(tmpDisplay, EGL_EXTENSIONS); + if (extensions) { + // color space + if (checkString("EGL_KHR_gl_colorspace", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; + } + + // create context + if (checkString("EGL_KHR_create_context", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_CREATE_CONTEXT; + s_Egl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE; + } + + // robustness + if (checkString("EGL_EXT_create_context_robustness", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_ROBUSTNESS; + } + + // no error + if (checkString("EGL_KHR_create_context_no_error", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_NO_ERROR; + } + + // flush control + if (checkString("EGL_KHR_context_flush_control", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + + // swap control + if (checkString("EGL_EXT_swap_control", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + + if (checkString("EGL_EXT_swap_control_tear", extensions)) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + } + + // part of the core API + s_Egl.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; + if (type & EGL_OPENGL_ES_BIT || type & EGL_OPENGL_ES2_BIT) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; + } + + // check if we support the core swap interval + if (s_Egl.swapInterval) { + s_Egl.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; + } + + if (s_Egl.apiType != EGL_OPENGL_API) { + if (s_Egl.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { + s_Egl.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE; + s_Egl.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; + } + + if (type & EGL_OPENGL_ES3_BIT) { + s_Egl.apiTypeBit = EGL_OPENGL_ES3_BIT; + } + } + + s_Egl.makeCurrent(tmpDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + s_Egl.destroyContext(tmpDisplay, context); + s_Egl.destroySurface(tmpDisplay, surface); + + s_Egl.info.api = api; + s_Egl.info.backend = PAL_GL_BACKEND_EGL; + s_Egl.allocator = allocator; + + if (tmpDisplay != display) { + // tmpDisplay is a seperate display + // terminate it + s_Egl.terminate(tmpDisplay); + } + + s_Egl.display = display; + s_Egl.instance = instance; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL eglShutdownGL() +{ + palFree(s_Egl.allocator, s_Egl.contextData); + if (s_Egl.display) { + s_Egl.terminate(s_Egl.display); + } + dlclose(s_Egl.handle); +} + +const PalGLInfo* PAL_CALL eglGetGLInfo() +{ + return &s_Egl.info; +} + +PalResult PAL_CALL eglEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs) +{ + int32_t configCount = 0; + int32_t maxConfigCount = 0; + if (configs) { + maxConfigCount = *count; + } + + // get the number of configs and filter the ones for opengl desktop + EGLint numConfigs = 0; + EGLint error = 0; + if (!s_Egl.getConfigs(s_Egl.display, nullptr, 0, &numConfigs)) { + error = s_Egl.getError(); + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_EGL, error); + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_Egl.allocator, configSize, 0); + if (!eglConfigs) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + s_Egl.getConfigs(s_Egl.display, eglConfigs, numConfigs, &numConfigs); + for (int i = 0; i < numConfigs; i++) { + // attributes we care about + EGLint surfaceType = 0, renderable = 0; + EGLint colorType = 0; + + EGLConfig config = eglConfigs[i]; + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_SURFACE_TYPE, &surfaceType); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_RENDERABLE_TYPE, &renderable); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_COLOR_BUFFER_TYPE, &colorType); + + // we need only opengl API configs + if (colorType != EGL_RGB_BUFFER) { + continue; + } + + if (s_Egl.apiType == EGL_OPENGL_ES3_BIT) { + // EGL_OPENGL_ES2_BIT + if (!(renderable & EGL_OPENGL_ES2_BIT) && !(renderable & EGL_OPENGL_ES3_BIT)) { + continue; + } + + } else if (s_Egl.apiType == EGL_OPENGL_ES2_BIT) { + if (!(renderable & EGL_OPENGL_ES2_BIT)) { + continue; + } + + } else { + if (!(renderable & EGL_OPENGL_BIT)) { + continue; + } + } + + if (!(surfaceType & EGL_WINDOW_BIT)) { + continue; + } + + if (configs && configCount < maxConfigCount) { + // get this only if user supplied an output buffer (PalGLFBConfig*) + PalGLFBConfig* fbConfig = &configs[configCount]; + fbConfig->index = i; + + EGLint redBits, greenBits, blueBits, alphaBits; + EGLint depthBits, stencilBits, samples; + + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_RED_SIZE, &redBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_GREEN_SIZE, &greenBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_BLUE_SIZE, &blueBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_ALPHA_SIZE, &alphaBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_DEPTH_SIZE, &depthBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_STENCIL_SIZE, &stencilBits); + s_Egl.getConfigAttrib(s_Egl.display, config, EGL_SAMPLES, &samples); + if (samples == 0) { + samples = 1; + } + + fbConfig->redBits = redBits; + fbConfig->greenBits = greenBits; + fbConfig->blueBits = blueBits; + fbConfig->alphaBits = alphaBits; + fbConfig->depthBits = depthBits; + fbConfig->stencilBits = stencilBits; + fbConfig->samples = samples; + + // True for EGL_WINDOW_BIT + fbConfig->doubleBuffer = PAL_TRUE; + fbConfig->stereo = PAL_FALSE; + + fbConfig->sRGB = PAL_FALSE; + if (s_Egl.info.extensions & PAL_GL_EXTENSION_COLORSPACE_SRGB) { + // since EGL does not have a bit to check SRGB support + // we check if all the color bits are greater than or equal to 8 + if (fbConfig->redBits >= 8 && fbConfig->greenBits >= 8 && fbConfig->blueBits >= 8) { + fbConfig->sRGB = PAL_TRUE; + } + } + } + configCount++; + } + + palFree(s_Egl.allocator, eglConfigs); + if (!configs) { + *count = configCount; + } + + return PAL_RESULT_SUCCESS; +} + +void* PAL_CALL eglGetGLProcAddress(const char* name) +{ + return s_Egl.getProcAddress(name); +} + +void PAL_CALL eglSetSwapInterval(int32_t interval) +{ + s_Egl.swapInterval(s_Egl.display, interval); +} + +const PalBool* PAL_CALL eglGetSupportedGLAPIs(void* instance) +{ + void* handle = dlopen("libEGL.so", RTLD_LAZY); + if (!handle) { + return nullptr; + } + + // clang-format off + eglGetProcAddressFn getProcAddress = (eglGetProcAddressFn)dlsym(handle, "eglGetProcAddress"); + eglInitializeFn initialize = (eglInitializeFn)getProcAddress("eglInitialize"); + eglTerminateFn terminate = (eglTerminateFn)getProcAddress("eglTerminate"); + eglGetDisplayFn getDisplay = (eglGetDisplayFn)getProcAddress("eglGetDisplay"); + eglQueryStringFn queryString = (eglQueryStringFn)getProcAddress("eglQueryString"); + if (!getProcAddress || !initialize || !terminate || !getDisplay || !queryString) { + return nullptr; + } + // clang-format on + + EGLDisplay display = getDisplay(instance); + if (display == EGL_NO_DISPLAY) { + return nullptr; + } + + if (!initialize(display, nullptr, nullptr)) { + return nullptr; + } + + const char* apis = queryString(display, EGL_CLIENT_APIS); + if (apis) { + if (checkString("OpenGL", apis)) { + s_SupportedAPIs[PAL_GL_API_OPENGL] = PAL_TRUE; + } else { + s_SupportedAPIs[PAL_GL_API_OPENGL] = PAL_FALSE; + } + + if (checkString("OpenGL_ES", apis)) { + s_SupportedAPIs[PAL_GL_API_OPENGL_ES] = PAL_TRUE; + } else { + s_SupportedAPIs[PAL_GL_API_OPENGL_ES] = PAL_FALSE; + } + } + + terminate(display); + dlclose(handle); + return s_SupportedAPIs; +} + +#endif // _PAL_HAS_EGL \ No newline at end of file diff --git a/src/opengl/egl/pal_egl.h b/src/opengl/egl/pal_egl.h new file mode 100644 index 00000000..6cbe0d2c --- /dev/null +++ b/src/opengl/egl/pal_egl.h @@ -0,0 +1,233 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal2/pal_opengl.h" + +#ifndef _PAL_EGL_H +#define _PAL_EGL_H + +#ifndef GL_VENDOR +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#endif // GL_VENDOR + +typedef unsigned int GLenum; +typedef unsigned char GLubyte; +typedef int32_t EGLint; +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; + +typedef unsigned long int khronos_uintptr_t; +typedef khronos_uintptr_t EGLNativeWindowType; + +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void* EGLDisplay; +typedef void* EGLNativeDisplayType; + +#ifndef EGL_OPENGL_API +#define EGL_CAST(type, value) ((type)(value)) +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_CLIENT_APIS 0x308D +#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) +#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType, 0) +#define EGL_GL_COLORSPACE 0x309D +#define EGL_COLORSPACE_sRGB 0x3089 +#define EGL_COLORSPACE_LINEAR 0x308A +#define EGL_COLOR_BUFFER_TYPE 0x303F +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_RENDERABLE_TYPE 0x3040 +#define EGL_RGB_BUFFER 0x308E +#define EGL_ALPHA_SIZE 0x3021 +#define EGL_BAD_ATTRIBUTE 0x3004 +#define EGL_BAD_CONFIG 0x3005 +#define EGL_BAD_CONTEXT 0x3006 +#define EGL_BAD_SURFACE 0x300D +#define EGL_EXTENSIONS 0x3055 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_MAX_PBUFFER_WIDTH 0x302C +#define EGL_NATIVE_VISUAL_ID 0x302E +#define EGL_NONE 0x3038 +#define EGL_HEIGHT 0x3056 +#define EGL_WIDTH 0x3057 +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES2_BIT 0x0004 +#define EGL_OPENGL_ES3_BIT 0x00000040 + +#define EGL_PBUFFER_BIT 0x0001 +#define EGL_RED_SIZE 0x3024 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_DEPTH_SIZE 0x3025 +#define EGL_SAMPLES 0x3031 +#define EGL_STENCIL_SIZE 0x3026 +#define EGL_SURFACE_TYPE 0x3033 +#define EGL_WINDOW_BIT 0x0004 +#define EGL_CONTEXT_MAJOR_VERSION 0x3098 +#define EGL_CONTEXT_MINOR_VERSION 0x30FB +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 + +#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 +#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB +#define EGL_CONTEXT_FLAGS_KHR 0x30FC +#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD +#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD +#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE +#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF +#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 +#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 +#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 +#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 +#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 +#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 +#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#define EGL_GL_COLORSPACE_KHR 0x309D +#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 +#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A +#endif // EGL header + +typedef void* (*eglGetProcAddressFn)(const char*); + +typedef EGLContext (*eglCreateContextFn)( + EGLDisplay, + EGLConfig, + EGLContext, + const EGLint*); + +typedef EGLBoolean (*eglDestroyContextFn)( + EGLDisplay, + EGLContext); + +typedef EGLBoolean (*eglMakeCurrentFn)( + EGLDisplay, + EGLSurface, + EGLSurface, + EGLContext); + +typedef EGLBoolean (*eglSwapBuffersFn)( + EGLDisplay, + EGLSurface); + +typedef EGLBoolean (*eglSwapIntervalFn)( + EGLDisplay, + EGLint); + +typedef EGLBoolean (*eglInitializeFn)( + EGLDisplay, + EGLint*, + EGLint*); + +typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); + +typedef EGLDisplay (*eglGetDisplayFn)(EGLNativeDisplayType); + +typedef EGLBoolean (*eglDestroySurfaceFn)( + EGLDisplay, + EGLSurface); + +typedef EGLSurface (*eglCreatePbufferSurfaceFn)( + EGLDisplay, + EGLConfig, + const EGLint*); + +typedef EGLBoolean (*eglChooseConfigFn)( + EGLDisplay, + const EGLint*, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLBoolean (*eglGetConfigAttribFn)( + EGLDisplay, + EGLConfig, + EGLint, + EGLint*); + +typedef EGLint (*eglGetErrorFn)(void); + +typedef EGLBoolean (*eglBindAPIFn)(EGLenum); + +typedef const char* (*eglQueryStringFn)( + EGLDisplay, + EGLint); + +typedef EGLBoolean (*eglGetConfigsFn)( + EGLDisplay, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLSurface (*eglCreateWindowSurfaceFn)( + EGLDisplay, + EGLConfig, + EGLNativeWindowType, + const EGLint*); + +typedef const GLubyte* (*glGetStringFn)(GLenum); +typedef void (*glClearFn)(uint32_t); + +typedef void (*glClearColorFn)( + float, + float, + float, + float); + +typedef struct { + PalBool used; + EGLSurface surface; + PalGLContext* context; +} ContextData; + +typedef struct { + int32_t maxContextData; + EGLenum apiType; + int apiTypeBit; + const PalAllocator* allocator; + + eglGetProcAddressFn getProcAddress; + eglCreateContextFn createContext; + eglDestroyContextFn destroyContext; + eglMakeCurrentFn makeCurrent; + eglSwapBuffersFn swapBuffers; + eglSwapIntervalFn swapInterval; + eglInitializeFn initialize; + eglTerminateFn terminate; + eglGetDisplayFn getDisplay; + eglDestroySurfaceFn destroySurface; + eglCreatePbufferSurfaceFn createPbufferSurface; + eglChooseConfigFn chooseConfig; + eglGetConfigAttribFn getConfigAttrib; + eglGetErrorFn getError; + eglBindAPIFn bindAPI; + eglQueryStringFn queryString; + eglGetConfigsFn getConfigs; + eglCreateWindowSurfaceFn createWindowSurface; + + glGetStringFn glGetString; + glClearColorFn glClearColor; + glClearFn glClear; + + void* handle; + void* instance; + ContextData* contextData; + EGLDisplay display; + PalGLInfo info; +} EGL; + +extern EGL s_Egl; + +#endif // _PAL_EGL_H \ No newline at end of file diff --git a/src/opengl/pal_opengl.c b/src/opengl/pal_opengl.c new file mode 100644 index 00000000..78f7692d --- /dev/null +++ b/src/opengl/pal_opengl.c @@ -0,0 +1,191 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal2/pal_opengl.h" +#include "pal_opengl_backends.h" +#include + +static OpenglBackend* s_Backend = nullptr; + +PalBool checkString( + const char* string, + const char* strings) +{ + const char* start = strings; + size_t stringLen = strlen(string); + + for (;;) { + const char* where = nullptr; + const char* terminator = nullptr; + + where = strstr(start, string); + if (!where) { + return PAL_FALSE; + } + + // the string was found, we find the terminator by adding the sizeof the strings + terminator = where + stringLen; + if (where == start || *(where - 1) == ' ') { + if (*terminator == ' ' || *terminator == '\0') { + return PAL_TRUE; + } + } + + start = terminator; + } +} + +PalResult PAL_CALL palInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator) +{ + if (!instance) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + // initialize the backends +#ifdef _WIN32 + PalResult result = wglInitGL(api, instance, allocator); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + s_Backend = &s_WglBackend; +#endif // _WIN32 + +#if _PAL_HAS_EGL + PalResult result = eglInitGL(api, instance, allocator); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + s_Backend = &s_EglBackend; +#endif // _PAL_HAS_EGL + + // check if we found a backend + if (!s_Backend) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palShutdownGL() +{ + s_Backend->shutdownGL(); +} + +const PalGLInfo* PAL_CALL palGetGLInfo() +{ + return s_Backend->getGLInfo(); +} + +PalResult PAL_CALL palEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs) +{ + if (!count) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->enumerateGLFBConfigs(count, configs); +} + +const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( + PalGLFBConfig* configs, + uint32_t count, + const PalGLFBConfig* desired) +{ + int32_t score = 0; + int32_t bestScore = 0x7FFFFFFF; + PalGLFBConfig* best = nullptr; + for (int32_t i = 0; i < count; i++) { + PalGLFBConfig* tmp = &configs[i]; + + // filter out hard constraints + if (desired->doubleBuffer && !tmp->doubleBuffer) { + continue; + } + + if (desired->stereo && !tmp->stereo) { + continue; + } + + score = 0; + + // score color bits + score += abs(tmp->redBits - desired->redBits); + score += abs(tmp->greenBits - desired->greenBits); + score += abs(tmp->blueBits - desired->blueBits); + score += abs(tmp->alphaBits - desired->alphaBits); + score += abs(tmp->depthBits - desired->depthBits); + score += abs(tmp->stencilBits - desired->stencilBits); + + // score soft constraints + if (desired->samples != tmp->samples) { + score += 1000; + } + + if (desired->sRGB != tmp->sRGB) { + score += 500; + } + + if (score < bestScore) { + bestScore = score; + best = &configs[i]; + } + } + + return best; +} + +PalResult PAL_CALL palCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext) +{ + if (!info || !outContext) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->createGLContext(info, outContext); +} + +void PAL_CALL palDestroyGLContext(PalGLContext* context) +{ + s_Backend->destroyGLContext(context); +} + +PalResult PAL_CALL palMakeContextCurrent( + PalGLWindow* glWindow, + PalGLContext* context) +{ + return s_Backend->makeContextCurrent(glWindow, context); +} + +void* PAL_CALL palGetGLProcAddress(const char* name) +{ + return s_Backend->getGLProcAddress(name); +} + +PalResult PAL_CALL palSwapBuffers( + PalGLWindow* glWindow, + PalGLContext* context) +{ + return s_Backend->swapBuffers(glWindow, context); +} + +void PAL_CALL palSetSwapInterval(int32_t interval) +{ + s_Backend->setSwapInterval(interval); +} + +const PalBool* PAL_CALL palGetSupportedGLAPIs(void* instance) +{ +#if _PAL_HAS_EGL + return eglGetSupportedGLAPIs(instance); +#elif defined(_WIN32) + return wglGetSupportedGLAPIs(instance); +#endif // _PAL_HAS_EGL +} \ No newline at end of file diff --git a/src/opengl/pal_opengl_backends.h b/src/opengl/pal_opengl_backends.h new file mode 100644 index 00000000..e2d49cd8 --- /dev/null +++ b/src/opengl/pal_opengl_backends.h @@ -0,0 +1,151 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_OPENGL_BACKENDS_H +#define _PAL_OPENGL_BACKENDS_H + +#include "pal2/pal_opengl.h" +#include "pal_platform.h" + +// clang-format off +typedef struct { + void (*shutdownGL)(); + + const PalGLInfo* (*getGLInfo)(); + + PalResult (*enumerateGLFBConfigs)( + uint32_t* count, + PalGLFBConfig* configs); + + PalResult (*createGLContext)( + const PalGLContextCreateInfo* info, + PalGLContext** outContext); + + void (*destroyGLContext)(PalGLContext* context); + + PalResult (*makeContextCurrent)( + PalGLWindow* window, + PalGLContext* context); + + void* (*getGLProcAddress)(const char* name); + + PalResult (*swapBuffers)( + PalGLWindow* window, + PalGLContext* context); + + void (*setSwapInterval)(int32_t interval); + + const PalBool* (*GetSupportedGLAPIs)(void* instance); +} OpenglBackend; + +// ================================================== +// WGL +// ================================================== + +#ifdef _WIN32 +PalResult wglInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator); + +void wglShutdownGL(); + +const PalGLInfo* wglGetGLInfo(); + +PalResult wglEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs); + +PalResult wglCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext); + +void wglDestroyGLContext(PalGLContext* context); + +PalResult wglMakeContextCurrent( + PalGLWindow* window, + PalGLContext* context); + +void* wglGetGLProcAddress(const char* name); + +PalResult wglSwapBuffers( + PalGLWindow* window, + PalGLContext* context); + +void wglSetSwapInterval(int32_t interval); + +const PalBool* wglGetSupportedGLAPIs(void* instance); + +static OpenglBackend s_WglBackend = { + .shutdownGL = wglShutdownGL, + .getGLInfo = wglGetGLInfo, + .enumerateGLFBConfigs = wglEnumerateGLFBConfigs, + .createGLContext = wglCreateGLContext, + .destroyGLContext = wglDestroyGLContext, + .makeContextCurrent = wglMakeContextCurrent, + .getGLProcAddress = wglGetGLProcAddress, + .swapBuffers = wglSwapBuffers, + .setSwapInterval = wglSetSwapInterval, + .GetSupportedGLAPIs = wglGetSupportedGLAPIs}; + +#endif // _WIN32 + +// ================================================== +// EGL +// ================================================== + +#if _PAL_HAS_EGL +PalResult eglInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator); + +void eglShutdownGL(); + +const PalGLInfo* eglGetGLInfo(); + +PalResult eglEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs); + +PalResult eglCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext); + +void eglDestroyGLContext(PalGLContext* context); + +PalResult eglMakeContextCurrent( + PalGLWindow* window, + PalGLContext* context); + +void* eglGetGLProcAddress(const char* name); + +PalResult eglSwapBuffers( + PalGLWindow* window, + PalGLContext* context); + +void eglSetSwapInterval(int32_t interval); + +const PalBool* eglGetSupportedGLAPIs(void* instance); + +static OpenglBackend s_EglBackend = { + .shutdownGL = eglShutdownGL, + .getGLInfo = eglGetGLInfo, + .enumerateGLFBConfigs = eglEnumerateGLFBConfigs, + .createGLContext = eglCreateGLContext, + .destroyGLContext = eglDestroyGLContext, + .makeContextCurrent = eglMakeContextCurrent, + .getGLProcAddress = eglGetGLProcAddress, + .swapBuffers = eglSwapBuffers, + .setSwapInterval = eglSetSwapInterval, + .GetSupportedGLAPIs = eglGetSupportedGLAPIs}; + +// clang-format on + +#endif // _PAL_HAS_EGL + +#endif // _PAL_OPENGL_BACKENDS_H \ No newline at end of file diff --git a/src/opengl/pal_opengl_linux.c b/src/opengl/pal_opengl_linux.c deleted file mode 100644 index c8b3b6a8..00000000 --- a/src/opengl/pal_opengl_linux.c +++ /dev/null @@ -1,1348 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200112L -#include "pal/pal_opengl.h" - -#include -#include -#include -#include -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#ifndef GL_VENDOR -#define GL_VENDOR 0x1F00 -#define GL_RENDERER 0x1F01 -#define GL_VERSION 0x1F02 -#define GL_EXTENSIONS 0x1F03 -#endif // GL_VENDOR - -typedef unsigned int GLenum; -typedef unsigned char GLubyte; -typedef int32_t EGLint; -typedef unsigned int EGLBoolean; -typedef unsigned int EGLenum; - -typedef unsigned long int khronos_uintptr_t; -typedef khronos_uintptr_t EGLNativeWindowType; - -typedef void* EGLConfig; -typedef void* EGLSurface; -typedef void* EGLContext; -typedef void* EGLDisplay; -typedef void* EGLNativeDisplayType; - -#ifndef EGL_OPENGL_API -#define EGL_CAST(type, value) ((type)(value)) -#define EGL_OPENGL_API 0x30A2 -#define EGL_OPENGL_BIT 0x0008 -#define EGL_OPENGL_ES_BIT 0x0001 -#define EGL_OPENGL_ES_API 0x30A0 -#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) -#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) -#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) -#define EGL_DEFAULT_DISPLAY EGL_CAST(EGLNativeDisplayType, 0) -#define EGL_GL_COLORSPACE 0x309D -#define EGL_COLORSPACE_sRGB 0x3089 -#define EGL_COLORSPACE_LINEAR 0x308A -#define EGL_COLOR_BUFFER_TYPE 0x303F -#define EGL_OPENGL_ES_BIT 0x0001 -#define EGL_OPENGL_ES_API 0x30A0 -#define EGL_RENDERABLE_TYPE 0x3040 -#define EGL_RGB_BUFFER 0x308E -#define EGL_ALPHA_SIZE 0x3021 -#define EGL_BAD_ATTRIBUTE 0x3004 -#define EGL_BAD_CONFIG 0x3005 -#define EGL_BAD_CONTEXT 0x3006 -#define EGL_BAD_SURFACE 0x300D -#define EGL_EXTENSIONS 0x3055 -#define EGL_GREEN_SIZE 0x3023 -#define EGL_MAX_PBUFFER_WIDTH 0x302C -#define EGL_NATIVE_VISUAL_ID 0x302E -#define EGL_NONE 0x3038 -#define EGL_HEIGHT 0x3056 -#define EGL_WIDTH 0x3057 -#define EGL_OPENGL_ES_BIT 0x0001 -#define EGL_OPENGL_ES2_BIT 0x0004 -#define EGL_OPENGL_ES3_BIT 0x00000040 - -#define EGL_PBUFFER_BIT 0x0001 -#define EGL_RED_SIZE 0x3024 -#define EGL_BLUE_SIZE 0x3022 -#define EGL_DEPTH_SIZE 0x3025 -#define EGL_SAMPLES 0x3031 -#define EGL_STENCIL_SIZE 0x3026 -#define EGL_SURFACE_TYPE 0x3033 -#define EGL_WINDOW_BIT 0x0004 -#define EGL_CONTEXT_MAJOR_VERSION 0x3098 -#define EGL_CONTEXT_MINOR_VERSION 0x30FB -#define EGL_CONTEXT_CLIENT_VERSION 0x3098 - -#define EGL_CONTEXT_MAJOR_VERSION_KHR 0x3098 -#define EGL_CONTEXT_MINOR_VERSION_KHR 0x30FB -#define EGL_CONTEXT_FLAGS_KHR 0x30FC -#define EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR 0x30FD -#define EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR 0x31BD -#define EGL_NO_RESET_NOTIFICATION_KHR 0x31BE -#define EGL_LOSE_CONTEXT_ON_RESET_KHR 0x31BF -#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0x00000001 -#define EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR 0x00000002 -#define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 -#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR 0x00000001 -#define EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR 0x00000002 -#define EGL_OPENGL_ES3_BIT_KHR 0x00000040 -#define EGL_CONTEXT_OPENGL_NO_ERROR_KHR 0x31B3 -#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 -#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 -#define EGL_GL_COLORSPACE_KHR 0x309D -#define EGL_GL_COLORSPACE_SRGB_KHR 0x3089 -#define EGL_GL_COLORSPACE_LINEAR_KHR 0x308A -#endif // EGL header - -typedef void* (*eglGetProcAddressFn)(const char*); - -typedef EGLContext (*eglCreateContextFn)( - EGLDisplay, - EGLConfig, - EGLContext, - const EGLint*); - -typedef EGLBoolean (*eglDestroyContextFn)( - EGLDisplay, - EGLContext); - -typedef EGLBoolean (*eglMakeCurrentFn)( - EGLDisplay, - EGLSurface, - EGLSurface, - EGLContext); - -typedef EGLBoolean (*eglSwapBuffersFn)( - EGLDisplay, - EGLSurface); - -typedef EGLBoolean (*eglSwapIntervalFn)( - EGLDisplay, - EGLint); - -typedef EGLBoolean (*eglInitializeFn)( - EGLDisplay, - EGLint*, - EGLint*); - -typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); - -typedef EGLDisplay (*eglGetDisplayFn)(EGLNativeDisplayType); - -typedef EGLBoolean (*eglDestroySurfaceFn)( - EGLDisplay, - EGLSurface); - -typedef EGLSurface (*eglCreatePbufferSurfaceFn)( - EGLDisplay, - EGLConfig, - const EGLint*); - -typedef EGLBoolean (*eglChooseConfigFn)( - EGLDisplay, - const EGLint*, - EGLConfig*, - EGLint, - EGLint*); - -typedef EGLBoolean (*eglGetConfigAttribFn)( - EGLDisplay, - EGLConfig, - EGLint, - EGLint*); - -typedef EGLint (*eglGetErrorFn)(void); - -typedef EGLBoolean (*eglBindAPIFn)(EGLenum); - -typedef const char* (*eglQueryStringFn)( - EGLDisplay, - EGLint); - -typedef EGLBoolean (*eglGetConfigsFn)( - EGLDisplay, - EGLConfig*, - EGLint, - EGLint*); - -typedef EGLSurface (*eglCreateWindowSurfaceFn)( - EGLDisplay, - EGLConfig, - EGLNativeWindowType, - const EGLint*); - -typedef const GLubyte* (*glGetStringFn)(GLenum); -typedef void(PAL_GL_APIENTRY* glClearFn)(Uint32); - -typedef void(PAL_GL_APIENTRY* glClearColorFn)( - float, - float, - float, - float); - -typedef struct { - bool used; - EGLSurface surface; - PalGLContext* context; -} ContextData; - -typedef struct { - bool initialized; - Int32 maxContextData; - EGLenum apiType; - int apiTypeBit; - const PalAllocator* allocator; - - eglGetProcAddressFn eglGetProcAddress; - eglCreateContextFn eglCreateContext; - eglDestroyContextFn eglDestroyContext; - eglMakeCurrentFn eglMakeCurrent; - eglSwapBuffersFn eglSwapBuffers; - eglSwapIntervalFn eglSwapInterval; - eglInitializeFn eglInitialize; - eglTerminateFn eglTerminate; - eglGetDisplayFn eglGetDisplay; - eglDestroySurfaceFn eglDestroySurface; - eglCreatePbufferSurfaceFn eglCreatePbufferSurface; - eglChooseConfigFn eglChooseConfig; - eglGetConfigAttribFn eglGetConfigAttrib; - eglGetErrorFn eglGetError; - eglBindAPIFn eglBindAPI; - eglQueryStringFn eglQueryString; - eglGetConfigsFn eglGetConfigs; - eglCreateWindowSurfaceFn eglCreateWindowSurface; - - glGetStringFn glGetString; - glClearColorFn glClearColor; - glClearFn glClear; - - void* handle; - void* platformDisplay; - ContextData* contextData; - EGLDisplay display; - PalGLInfo info; -} GLLinux; - -static GLLinux s_GL = {0}; - -// ================================================== -// Internal API -// ================================================== - -static inline bool checkExtension( - const char* extension, - const char* extensions) -{ - const char* start = extensions; - size_t extensionLen = strlen(extension); - - for (;;) { - const char* where = nullptr; - const char* terminator = nullptr; - - where = strstr(start, extension); - if (!where) { - return false; - } - - // the extension was found, we find the terminator by adding the sizeof - // the extension - terminator = where + extensionLen; - if (where == start || *(where - 1) == ' ') { - if (*terminator == ' ' || *terminator == '\0') { - return true; - } - } - - start = terminator; - } -} - -static ContextData* getFreeContextData() -{ - for (int i = 0; i < s_GL.maxContextData; ++i) { - if (!s_GL.contextData[i].used) { - s_GL.contextData[i].used = true; - return &s_GL.contextData[i]; - } - } - - // resize the data array - // this will almost not reach here since most setups are 1-4 monitors - ContextData* data = nullptr; - int count = s_GL.maxContextData * 2; // double the size - int freeIndex = s_GL.maxContextData + 1; - data = palAllocate(s_GL.allocator, sizeof(ContextData) * count, 0); - if (data) { - memcpy( - data, - s_GL.contextData, - s_GL.maxContextData * sizeof(ContextData)); - - palFree(s_GL.allocator, s_GL.contextData); - s_GL.contextData = data; - s_GL.maxContextData = count; - - s_GL.contextData[freeIndex].used = true; - return &s_GL.contextData[freeIndex]; - } - return nullptr; -} - -static ContextData* findContextData(PalGLContext* context) -{ - for (int i = 0; i < s_GL.maxContextData; ++i) { - if (s_GL.contextData[i].used && - s_GL.contextData[i].context == context) { - return &s_GL.contextData[i]; - } - } -} - -static void freeContextData(PalGLContext* context) -{ - for (int i = 0; i < s_GL.maxContextData; ++i) { - if (s_GL.contextData[i].used && - s_GL.contextData[i].context == context) { - s_GL.contextData[i].used = false; - } - } -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palInitGL(const PalAllocator* allocator) -{ - if (s_GL.initialized) { - return PAL_RESULT_SUCCESS; - } - - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - if (!s_GL.platformDisplay) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_GL.maxContextData = 16; // initial size - s_GL.contextData = palAllocate( - s_GL.allocator, - sizeof(ContextData) * s_GL.maxContextData, - 0); - - if (!s_GL.maxContextData) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_GL.handle = dlopen("libEGL.so", RTLD_LAZY); - if (!s_GL.handle) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // clang-format off - - s_GL.eglGetProcAddress = (eglGetProcAddressFn)dlsym( - s_GL.handle, - "eglGetProcAddress"); - - s_GL.eglCreateContext = (eglCreateContextFn)s_GL.eglGetProcAddress( - "eglCreateContext"); - - s_GL.eglDestroyContext = (eglDestroyContextFn)s_GL.eglGetProcAddress( - "eglDestroyContext"); - - s_GL.eglDestroySurface = (eglDestroySurfaceFn)s_GL.eglGetProcAddress( - "eglDestroySurface"); - - s_GL.eglMakeCurrent = (eglMakeCurrentFn)s_GL.eglGetProcAddress( - "eglMakeCurrent"); - - s_GL.eglSwapBuffers = (eglSwapBuffersFn)s_GL.eglGetProcAddress( - "eglSwapBuffers"); - - s_GL.eglSwapInterval = (eglSwapIntervalFn)s_GL.eglGetProcAddress( - "eglSwapInterval"); - - s_GL.eglInitialize = (eglInitializeFn)s_GL.eglGetProcAddress( - "eglInitialize"); - - s_GL.eglTerminate = (eglTerminateFn)s_GL.eglGetProcAddress("eglTerminate"); - - s_GL.eglGetDisplay = (eglGetDisplayFn)s_GL.eglGetProcAddress( - "eglGetDisplay"); - - s_GL.eglCreatePbufferSurface = (eglCreatePbufferSurfaceFn)s_GL.eglGetProcAddress( - "eglCreatePbufferSurface"); - - s_GL.eglChooseConfig = (eglChooseConfigFn)s_GL.eglGetProcAddress( - "eglChooseConfig"); - - s_GL.eglGetConfigAttrib = (eglGetConfigAttribFn)s_GL.eglGetProcAddress( - "eglGetConfigAttrib"); - - s_GL.eglGetError = (eglGetErrorFn)s_GL.eglGetProcAddress("eglGetError"); - s_GL.eglBindAPI = (eglBindAPIFn)s_GL.eglGetProcAddress("eglBindAPI"); - - s_GL.eglQueryString = (eglQueryStringFn)s_GL.eglGetProcAddress( - "eglQueryString"); - - s_GL.eglGetConfigs = (eglGetConfigsFn)s_GL.eglGetProcAddress( - "eglGetConfigs"); - - s_GL.eglCreateWindowSurface = (eglCreateWindowSurfaceFn)s_GL.eglGetProcAddress( - "eglCreateWindowSurface"); - - s_GL.glClearColor = (glClearColorFn)s_GL.eglGetProcAddress("glClearColor"); - s_GL.glClear = (glClearFn)s_GL.eglGetProcAddress("glClear"); - - if (!s_GL.eglBindAPI || - !s_GL.eglChooseConfig || - !s_GL.eglCreateContext || - !s_GL.eglCreatePbufferSurface || - !s_GL.eglDestroyContext || - !s_GL.eglDestroySurface || - !s_GL.eglGetConfigAttrib || - !s_GL.eglGetDisplay || - !s_GL.eglGetError || - !s_GL.eglGetProcAddress || - !s_GL.eglInitialize || - !s_GL.eglMakeCurrent || - !s_GL.eglSwapBuffers || - !s_GL.eglSwapInterval || - !s_GL.eglTerminate || - !s_GL.eglQueryString || - !s_GL.eglGetConfigs || - !s_GL.eglCreateWindowSurface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // get backend type - const char* session = getenv("XDG_SESSION_TYPE"); - if (session) { - if (strcmp(session, "wayland") == 0) { - s_GL.apiType = EGL_OPENGL_ES_API; - s_GL.apiTypeBit = EGL_OPENGL_ES2_BIT; // default - - } else { - s_GL.apiType = EGL_OPENGL_API; - s_GL.apiTypeBit = EGL_OPENGL_BIT; - } - } - - if (!s_GL.eglBindAPI(s_GL.apiType)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLDisplay display = s_GL.eglGetDisplay(s_GL.platformDisplay); - EGLDisplay * tmpDisplay = EGL_NO_DISPLAY; - if (display == EGL_NO_DISPLAY) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - if (!s_GL.eglInitialize(display, nullptr, nullptr)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // use a simple FBConfig - EGLConfig config; - int numConfigs; - EGLint type; - EGLint attribs[] = { - EGL_RENDERABLE_TYPE, - s_GL.apiTypeBit, - EGL_SURFACE_TYPE, - EGL_PBUFFER_BIT, - EGL_NONE}; - - s_GL.eglChooseConfig(display, attribs, &config, 1, &numConfigs); - if (!config || numConfigs == 0) { - // API bind type might not support puffer - // create a default display and use that - tmpDisplay = s_GL.eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - if (!s_GL.eglInitialize(tmpDisplay, nullptr, nullptr)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } else { - // set the tmp display to our display - tmpDisplay = display; - } - - s_GL.eglChooseConfig(tmpDisplay, attribs, &config, 1, &numConfigs); - if (!config) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_GL.eglGetConfigAttrib(tmpDisplay, config, EGL_RENDERABLE_TYPE, &type); - if (!(type & s_GL.apiTypeBit)) { - // we must support the required API - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // Since we don't want to create dummy window to get the driver info - // we use EGL_OPENGL_ES2_BIT to create without a window - if (s_GL.apiType == EGL_OPENGL_API) { - if (!(type & EGL_OPENGL_ES2_BIT)) { - // FIXME: create a dummy window if EGL_OPENGL_ES2_BIT - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - EGLSurface surface = EGL_NO_SURFACE; - EGLint pBufferAttribs[] = { - EGL_WIDTH, 1, - EGL_HEIGHT, 1, - EGL_NONE}; - - surface = s_GL.eglCreatePbufferSurface( - tmpDisplay, - config, - pBufferAttribs); - - if (surface == EGL_NO_SURFACE) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // create a dummy context - EGLContext context = EGL_NO_CONTEXT; - if (s_GL.apiType == EGL_OPENGL_API) { - EGLint contextAttrib[] = { - EGL_CONTEXT_MAJOR_VERSION, 2, - EGL_CONTEXT_MINOR_VERSION, 1, - EGL_NONE}; - - context = s_GL.eglCreateContext( - tmpDisplay, - config, - EGL_NO_CONTEXT, - contextAttrib); - - } else { - EGLint contextAttrib[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE}; - - context = s_GL.eglCreateContext( - tmpDisplay, - config, - EGL_NO_CONTEXT, - contextAttrib); - } - - if (context == EGL_NO_CONTEXT) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_GL.eglMakeCurrent(tmpDisplay, surface, surface, context); - s_GL.glGetString = (glGetStringFn)s_GL.eglGetProcAddress("glGetString"); - - const char* version = (const char*)s_GL.glGetString(GL_VERSION); - if (version) { - if (s_GL.apiType == EGL_OPENGL_API) { - sscanf(version, "%d.%d", &s_GL.info.major, &s_GL.info.minor); - } else { - sscanf(version + 10, "%d.%d", &s_GL.info.major, &s_GL.info.minor); - } - } - - const char* renderer = (const char*)s_GL.glGetString(GL_RENDERER); - const char* vendor = (const char*)s_GL.glGetString(GL_VENDOR); - strcpy(s_GL.info.vendor, vendor); - strcpy(s_GL.info.version, version); - strcpy(s_GL.info.graphicsCard, renderer); - - // EGL extensions can be queried without a bound context - // we just do that over here after making the context current - const char* extensions = s_GL.eglQueryString(tmpDisplay, EGL_EXTENSIONS); - if (extensions) { - // color space - if (checkExtension("EGL_KHR_gl_colorspace", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; - } - - // create context - if (checkExtension("EGL_KHR_create_context", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_CREATE_CONTEXT; - s_GL.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE; - } - - // robustness - if (checkExtension("EGL_EXT_create_context_robustness", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_ROBUSTNESS; - } - - // no error - if (checkExtension("EGL_KHR_create_context_no_error", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_NO_ERROR; - } - - // flush control - if (checkExtension("EGL_KHR_context_flush_control", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; - } - - // swap control - if (checkExtension("EGL_EXT_swap_control", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; - } - - if (checkExtension("EGL_EXT_swap_control_tear", extensions)) { - s_GL.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; - } - } - - // part of the core API - s_GL.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; - - if (type & EGL_OPENGL_ES_BIT || - type & EGL_OPENGL_ES2_BIT) { - s_GL.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; - } - - // check if we support the core swap interval - if (s_GL.eglSwapInterval) { - s_GL.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; - } - - if (s_GL.apiType != EGL_OPENGL_API) { - if (s_GL.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { - s_GL.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE; - s_GL.info.extensions &= ~PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; - } - - if (type & EGL_OPENGL_ES3_BIT) { - s_GL.apiTypeBit = EGL_OPENGL_ES3_BIT; - } - } - - s_GL.eglMakeCurrent( - tmpDisplay, - EGL_NO_SURFACE, - EGL_NO_SURFACE, - EGL_NO_CONTEXT); - - // clang-format on - - s_GL.eglDestroyContext(tmpDisplay, context); - s_GL.eglDestroySurface(tmpDisplay, surface); - - s_GL.allocator = allocator; - s_GL.initialized = true; - - if (tmpDisplay != display) { - // tmpDisplay is a seperate display - // terminate it - s_GL.eglTerminate(tmpDisplay); - } - - s_GL.display = display; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palShutdownGL() -{ - if (!s_GL.initialized) { - return; - } - - palFree(s_GL.allocator, s_GL.contextData); - if (s_GL.display) { - s_GL.eglTerminate(s_GL.display); - } - - dlclose(s_GL.handle); - s_GL.initialized = false; -} - -const PalGLInfo* PAL_CALL palGetGLInfo() -{ - if (!s_GL.initialized) { - return nullptr; - } - return &s_GL.info; -} - -PalResult PAL_CALL palEnumerateGLFBConfigs( - PalGLWindow* glWindow, - Int32* count, - PalGLFBConfig* configs) -{ - if (!s_GL.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!count) { - return PAL_RESULT_NULL_POINTER; - } - - if (count == 0 && configs) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - Int32 configCount = 0; - Int32 maxConfigCount = 0; - - if (configs) { - maxConfigCount = *count; - } - - // get the number of configs and filter the ones for opengl desktop - EGLint numConfigs = 0; - if (!s_GL.eglGetConfigs(s_GL.display, nullptr, 0, &numConfigs)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint configSize = sizeof(EGLConfig) * numConfigs; - EGLConfig* eglConfigs = palAllocate(s_GL.allocator, configSize, 0); - if (!eglConfigs) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_GL.eglGetConfigs(s_GL.display, eglConfigs, numConfigs, &numConfigs); - for (int i = 0; i < numConfigs; i++) { - // attributes we care about - EGLint surfaceType = 0, renderable = 0; - EGLint colorType = 0; - - EGLConfig config = eglConfigs[i]; - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_SURFACE_TYPE, - &surfaceType); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_RENDERABLE_TYPE, - &renderable); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_COLOR_BUFFER_TYPE, - &colorType); - - // we need only opengl API configs - if (colorType != EGL_RGB_BUFFER) { - continue; - } - - if (s_GL.apiType == EGL_OPENGL_ES3_BIT) { - // EGL_OPENGL_ES2_BIT - if (!(renderable & EGL_OPENGL_ES2_BIT) && - !(renderable & EGL_OPENGL_ES3_BIT)) { - continue; - } - - } else if (s_GL.apiType == EGL_OPENGL_ES2_BIT) { - if (!(renderable & EGL_OPENGL_ES2_BIT)) { - continue; - } - - } else { - if (!(renderable & EGL_OPENGL_BIT)) { - continue; - } - } - - if (!(surfaceType & EGL_WINDOW_BIT)) { - continue; - } - - if (configs && configCount < maxConfigCount) { - // get this only if user supplied an output buffer (PalGLFBConfig*) - PalGLFBConfig* fbConfig = &configs[configCount]; - fbConfig->index = i; - - EGLint redBits, greenBits, blueBits, alphaBits; - EGLint depthBits, stencilBits, samples; - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_RED_SIZE, - &redBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_GREEN_SIZE, - &greenBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_BLUE_SIZE, - &blueBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_ALPHA_SIZE, - &alphaBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_DEPTH_SIZE, - &depthBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_STENCIL_SIZE, - &stencilBits); - - s_GL.eglGetConfigAttrib( - s_GL.display, - config, - EGL_SAMPLES, - &samples); - - if (samples == 0) { - samples = 1; - } - - fbConfig->redBits = redBits; - fbConfig->greenBits = greenBits; - fbConfig->blueBits = blueBits; - fbConfig->alphaBits = alphaBits; - fbConfig->depthBits = depthBits; - fbConfig->stencilBits = stencilBits; - fbConfig->samples = samples; - - // True for EGL_WINDOW_BIT - fbConfig->doubleBuffer = true; - fbConfig->stereo = false; - - if (s_GL.info.extensions & PAL_GL_EXTENSION_COLORSPACE_SRGB) { - // since EGL does not have a bit to check SRGB support - // we check if all the color bits are greater than or equal to 8 - if (fbConfig->redBits >= 8 && fbConfig->greenBits >= 8 && - fbConfig->blueBits >= 8) { - fbConfig->sRGB = true; - } - } else { - fbConfig->sRGB = false; - } - } - configCount++; - } - - palFree(s_GL.allocator, eglConfigs); - if (!configs) { - *count = configCount; - } - - return PAL_RESULT_SUCCESS; -} - -const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( - PalGLFBConfig* configs, - Int32 count, - const PalGLFBConfig* desired) -{ - if (!s_GL.initialized) { - return nullptr; - } - - if (!configs || !desired) { - return nullptr; - } - - if (count == 0) { - return nullptr; - } - - Int32 score = 0; - Int32 bestScore = 0x7FFFFFFF; - PalGLFBConfig* best = nullptr; - for (Int32 i = 0; i < count; i++) { - PalGLFBConfig* tmp = &configs[i]; - - // filter out hard constraints - if (desired->doubleBuffer && !tmp->doubleBuffer) { - continue; - } - - if (desired->stereo && !tmp->stereo) { - continue; - } - - score = 0; - - // score color bits - score += abs(tmp->redBits - desired->redBits); - score += abs(tmp->greenBits - desired->greenBits); - score += abs(tmp->blueBits - desired->blueBits); - score += abs(tmp->alphaBits - desired->alphaBits); - score += abs(tmp->depthBits - desired->depthBits); - score += abs(tmp->stencilBits - desired->stencilBits); - - // score soft constraints - if (desired->samples != tmp->samples) { - score += 1000; - } - - if (desired->sRGB != tmp->sRGB) { - score += 500; - } - - if (score < bestScore) { - bestScore = score; - best = &configs[i]; - } - } - - return best; -} - -// ================================================== -// Context -// ================================================== - -PalResult PAL_CALL palCreateGLContext( - const PalGLContextCreateInfo* info, - PalGLContext** outContext) -{ - if (!s_GL.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!info || !outContext) { - return PAL_RESULT_NULL_POINTER; - } - - if (!info->window || !info->fbConfig) { - return PAL_RESULT_NULL_POINTER; - } - - // check if the window was created with the same display - // the opengl system is using - if (info->window->display != s_GL.platformDisplay) { - return PAL_RESULT_INVALID_GL_WINDOW; - } - - // check support for requested features - if (info->profile != PAL_GL_PROFILE_NONE) { - if (!(s_GL.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->forward) { - if (!(s_GL.info.extensions & PAL_GL_EXTENSION_CREATE_CONTEXT)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { - if (!(s_GL.info.extensions & PAL_GL_EXTENSION_ROBUSTNESS)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->noError) { - if (!(s_GL.info.extensions & PAL_GL_EXTENSION_NO_ERROR)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { - if (!(s_GL.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - // clang-format off - - // check version - bool valid = info->major < s_GL.info.major || - (info->major == s_GL.info.major && info->minor <= s_GL.info.minor); - // clang-format on - - if (!valid) { - return PAL_RESULT_INVALID_GL_VERSION; - } - - ContextData* data = getFreeContextData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - // we need to get the EGL config from the user supplied index - EGLint numConfigs = 0; - if (!s_GL.eglGetConfigs(s_GL.display, nullptr, 0, &numConfigs)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint configSize = sizeof(EGLConfig) * numConfigs; - EGLConfig* eglConfigs = palAllocate(s_GL.allocator, configSize, 0); - if (!eglConfigs) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_GL.eglGetConfigs(s_GL.display, eglConfigs, numConfigs, &numConfigs); - EGLConfig config = eglConfigs[info->fbConfig->index]; - - EGLContext* share = nullptr; - if (info->shareContext) { - share = (EGLContext)info->shareContext; - } else { - share = EGL_NO_CONTEXT; - } - - Int32 attribs[40]; - Int32 index = 0; - Int32 profile = 0; - Int32 flags = 0; - - // set context attributes - // the first element is the key and the second is the value - if (s_GL.apiType == EGL_OPENGL_API) { - // set version - attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR; // key - attribs[index++] = info->major; // value - - attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR; - attribs[index++] = info->minor; - - // set profile mask - if (info->profile != PAL_GL_PROFILE_NONE) { - attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; - - if (info->profile == PAL_GL_PROFILE_COMPATIBILITY) { - profile = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; - } else if (info->profile == PAL_GL_PROFILE_CORE) { - profile = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR; - } - - attribs[index++] = info->profile; - } - - // set forward flag - if (info->forward) { - flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; - } - - } else { - attribs[index++] = EGL_CONTEXT_CLIENT_VERSION; - // our configs support EGL_OPENGL_ES2_BIT and EGL_OPENGL_ES3_BIT - attribs[index++] = info->major; - } - - // set debug flag - if (info->debug) { - flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; - } - - // set robustness - if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { - flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; - attribs[index++] = EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR; - - if (info->reset == PAL_GL_CONTEXT_RESET_LOSE_CONTEXT) { - attribs[index++] = EGL_LOSE_CONTEXT_ON_RESET_KHR; - - } else if (info->reset == PAL_GL_CONTEXT_RESET_NO_NOTIFICATION) { - attribs[index++] = EGL_NO_RESET_NOTIFICATION_KHR; - } - } - - // set no error - if (info->noError) { - attribs[index++] = EGL_CONTEXT_OPENGL_NO_ERROR_KHR; - attribs[index++] = true; - } - - // release - if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { - attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_KHR; - attribs[index++] = EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR; - } - - if (flags) { - attribs[index++] = EGL_CONTEXT_FLAGS_KHR; - attribs[index++] = flags; - } - - attribs[index++] = EGL_NONE; - - // clang-format off - // create context - EGLContext context = s_GL.eglCreateContext( - s_GL.display, - config, - share, - attribs); - // clang-format on - - if (context == EGL_NO_CONTEXT) { - EGLint error = s_GL.eglGetError(); - if (error == EGL_BAD_CONFIG) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - - } else if (error == EGL_BAD_ATTRIBUTE) { - // we just return invalid version - // it could be invalid profile - return PAL_RESULT_INVALID_GL_VERSION; - - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - // create a window surface - // The only attrib we want is colorspace - EGLint surfaceAttribs[3]; - surfaceAttribs[0] = EGL_GL_COLORSPACE; - if (info->fbConfig->sRGB) { - surfaceAttribs[1] = EGL_GL_COLORSPACE_SRGB_KHR; - } else { - surfaceAttribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR; - } - surfaceAttribs[2] = EGL_NONE; - - EGLSurface surface = s_GL.eglCreateWindowSurface( - s_GL.display, - config, - (EGLNativeWindowType)info->window->window, - surfaceAttribs); - - if (surface == EGL_NO_SURFACE) { - EGLint error = s_GL.eglGetError(); - if (error == EGL_BAD_CONFIG) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - // make the context current and swap for the first time - // this will make the window visible - if (s_GL.eglMakeCurrent(s_GL.display, surface, surface, context)) { - s_GL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - s_GL.glClear(0x00004000); - s_GL.eglSwapBuffers(s_GL.display, surface); - - // revert - s_GL.eglMakeCurrent( - s_GL.display, - EGL_NO_SURFACE, - EGL_NO_SURFACE, - EGL_NO_CONTEXT); - } - - palFree(s_GL.allocator, eglConfigs); - data->context = (PalGLContext*)context; - data->surface = surface; - - *outContext = (PalGLContext*)context; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyGLContext(PalGLContext* context) -{ - if (s_GL.initialized && context) { - ContextData* data = findContextData(context); - if (data) { - // make it not current if it was current - s_GL.eglMakeCurrent( - s_GL.display, - EGL_NO_SURFACE, - EGL_NO_SURFACE, - EGL_NO_CONTEXT); - - s_GL.eglDestroyContext(s_GL.display, (EGLContext)context); - s_GL.eglDestroySurface(s_GL.display, data->surface); - data->used = false; - } - } -} - -PalResult PAL_CALL palMakeContextCurrent( - PalGLWindow* glWindow, - PalGLContext* context) -{ - if (!s_GL.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if ((!glWindow && context) || (glWindow && !context)) { - return PAL_RESULT_NULL_POINTER; - } - - if (context && glWindow) { - ContextData* data = findContextData(context); - if (!data) { - return PAL_RESULT_INVALID_GL_CONTEXT; - } - - EGLint ret = s_GL.eglMakeCurrent( - s_GL.display, - data->surface, - data->surface, - (EGLConfig)context); - - if (!ret) { - EGLint error = s_GL.eglGetError(); - if (error == EGL_BAD_CONTEXT) { - return PAL_RESULT_INVALID_GL_CONTEXT; - - } else if (error == EGL_BAD_SURFACE) { - // since we always create a window surface - return PAL_RESULT_INVALID_GL_WINDOW; - - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - } else if (!context && !glWindow) { - s_GL.eglMakeCurrent( - s_GL.display, - EGL_NO_SURFACE, - EGL_NO_SURFACE, - EGL_NO_CONTEXT); - } - - return PAL_RESULT_SUCCESS; -} - -void* PAL_CALL palGLGetProcAddress(const char* name) -{ - if (!s_GL.initialized) { - return nullptr; - } - - return s_GL.eglGetProcAddress(name); -} - -PalResult PAL_CALL palSwapBuffers( - PalGLWindow* glWindow, - PalGLContext* context) -{ - if (!s_GL.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!context || !glWindow) { - return PAL_RESULT_NULL_POINTER; - } - - ContextData* data = findContextData(context); - if (!data) { - return PAL_RESULT_INVALID_GL_CONTEXT; - } - - if (!s_GL.eglSwapBuffers(s_GL.display, data->surface)) { - EGLint error = s_GL.eglGetError(); - if (error == EGL_BAD_CONTEXT) { - return PAL_RESULT_INVALID_GL_CONTEXT; - - } else if (error == EGL_BAD_SURFACE) { - // since we always create a window surface - return PAL_RESULT_INVALID_GL_WINDOW; - - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetSwapInterval(Int32 interval) -{ - if (!s_GL.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!s_GL.eglSwapInterval) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - - s_GL.eglSwapInterval(s_GL.display, interval); - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palGLSetInstance(void* instance) -{ - s_GL.platformDisplay = instance; -} - -const char* PAL_CALL palGLGetBackend() -{ - if (!s_GL.initialized) { - return nullptr; - } - - if (s_GL.apiType == EGL_OPENGL_API) { - return "egl"; - } else { - return "gles"; - } -} \ No newline at end of file diff --git a/src/opengl/pal_opengl_shared.h b/src/opengl/pal_opengl_shared.h new file mode 100644 index 00000000..82e4fd7a --- /dev/null +++ b/src/opengl/pal_opengl_shared.h @@ -0,0 +1,17 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_OPENGL_SHARED_H +#define _PAL_OPENGL_SHARED_H + +#include "pal2/pal_core.h" + +PalBool checkString( + const char* string, + const char* strings); + +#endif // _PAL_OPENGL_SHARED_H \ No newline at end of file diff --git a/src/opengl/pal_opengl_win32.c b/src/opengl/pal_opengl_win32.c deleted file mode 100644 index 0eb79f09..00000000 --- a/src/opengl/pal_opengl_win32.c +++ /dev/null @@ -1,1076 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#include "pal/pal_opengl.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// set unicode -#ifndef UNICODE -#define UNICODE -#endif // UNICODE - -#include -#include -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define PAL_GL_CLASS L"PALGLClass" - -// check to see if this is not defined yet -#ifndef GL_VENDOR -#define GL_VENDOR 0x1F00 -#define GL_RENDERER 0x1F01 -#define GL_VERSION 0x1F02 -#define GL_EXTENSIONS 0x1F03 -#endif // GL_VENDOR - -#ifndef WGL_NUMBER_PIXEL_FORMATS_ARB - -#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 -#define WGL_ACCELERATION_ARB 0x2003 -#define WGL_RED_BITS_ARB 0x2015 -#define WGL_GREEN_BITS_ARB 0x2017 -#define WGL_BLUE_BITS_ARB 0x2019 -#define WGL_ALPHA_BITS_ARB 0x201b - -#define WGL_SUPPORT_OPENGL_ARB 0x2010 -#define WGL_DRAW_TO_WINDOW_ARB 0x2001 -#define WGL_PIXEL_TYPE_ARB 0x2013 -#define WGL_DEPTH_BITS_ARB 0x2022 -#define WGL_STENCIL_BITS_ARB 0x2023 -#define WGL_STEREO_ARB 0x2012 -#define WGL_DOUBLE_BUFFER_ARB 0x2011 -#define WGL_SAMPLES_ARB 0x2042 -#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 -#define WGL_TYPE_RGBA_ARB 0x202b -#define WGL_NO_ACCELERATION_ARB 0x2025 - -#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 -#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 -#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 -#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 -#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 -#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 -#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 -#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 -#define WGL_CONTEXT_FLAGS_ARB 0x2094 - -#define ERROR_INVALID_PROFILE_ARB 0x2096 - -#endif // WGL_NUMBER_PIXEL_FORMATS_ARB - -typedef unsigned int GLenum; -typedef unsigned char GLubyte; - -// gdi functions -typedef int(WINAPI* ChoosePixelFormatFn)( - HDC, - CONST PIXELFORMATDESCRIPTOR*); - -typedef BOOL(WINAPI* SetPixelFormatFn)( - HDC, - int, - CONST PIXELFORMATDESCRIPTOR*); - -typedef int(WINAPI* DescribePixelFormatFn)( - HDC, - int, - UINT, - LPPIXELFORMATDESCRIPTOR); - -typedef int(WINAPI* GetPixelFormatFn)(HDC); - -typedef BOOL(WINAPI* SwapBuffersFn)(HDC); - -// wgl functions -typedef PROC(WINAPI* wglGetProcAddressFn)(LPCSTR); - -typedef HGLRC(WINAPI* wglCreateContextFn)(HDC); - -typedef BOOL(WINAPI* wglDeleteContextFn)(HGLRC); - -typedef BOOL(WINAPI* wglShareListsFn)( - HGLRC, - HGLRC); - -typedef BOOL(WINAPI* wglMakeCurrentFn)( - HDC, - HGLRC); - -// gl functions -typedef const GLubyte*(WINAPI* glGetStringFn)(GLenum); - -// extensions -typedef BOOL(WINAPI* wglChoosePixelFormatARBFn)( - HDC, - const int*, - const FLOAT*, - UINT, - int*, - UINT*); - -typedef BOOL(WINAPI* wglGetPixelFormatAttribivARBFn)( - HDC, - int, - int, - UINT, - const int*, - int*); - -typedef HGLRC(WINAPI* wglCreateContextAttribsARBFn)( - HDC, - HGLRC, - const int*); - -typedef BOOL(WINAPI* wglSwapIntervalEXTFn)(int); - -typedef const char*(WINAPI* wglGetExtensionsStringEXTFn)(); - -typedef const char*(WINAPI* wglGetExtensionsStringARBFn)(HDC); - -typedef struct { - SetPixelFormatFn setPixelFormat; - DescribePixelFormatFn describePixelFormat; - ChoosePixelFormatFn choosePixelFormat; - GetPixelFormatFn getPixelFormat; - SwapBuffersFn swapBuffers; - HINSTANCE handle; -} Gdi; - -typedef struct { - bool initialized; - wglGetProcAddressFn wglGetProcAddress; - wglCreateContextFn wglCreateContext; - wglDeleteContextFn wglDeleteContext; - wglMakeCurrentFn wglMakeCurrent; - wglShareListsFn wglShareLists; - glGetStringFn glGetString; - - wglCreateContextAttribsARBFn wglCreateContextAttribsARB; - wglChoosePixelFormatARBFn wglChoosePixelFormatARB; - wglSwapIntervalEXTFn wglSwapIntervalEXT; - wglGetExtensionsStringEXTFn wglGetExtensionsStringEXT; - wglGetExtensionsStringARBFn wglGetExtensionsStringARB; - wglGetPixelFormatAttribivARBFn wglGetPixelFormatAttribivARB; - - const PalAllocator* allocator; - HINSTANCE opengl; - HINSTANCE instance; - HWND window; - HDC hdc; - HGLRC context; - PalGLInfo info; -} Wgl; - -static Gdi s_Gdi = {0}; -static Wgl s_Wgl = {0}; - -// ================================================== -// Internal API -// ================================================== - -static inline bool checkExtension( - const char* extension, - const char* extensions) -{ - const char* start = extensions; - size_t extensionLen = strlen(extension); - - for (;;) { - const char* where = nullptr; - const char* terminator = nullptr; - - where = strstr(start, extension); - if (!where) { - return false; - } - - // the extension was found, we find the terminator by adding the sizeof - // the extension - terminator = where + extensionLen; - if (where == start || *(where - 1) == ' ') { - if (*terminator == ' ' || *terminator == '\0') { - return true; - } - } - - start = terminator; - } -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palInitGL(const PalAllocator* allocator) -{ - if (s_Wgl.initialized) { - return PAL_RESULT_SUCCESS; - } - - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - // get the instance - if (!s_Wgl.instance) { - s_Wgl.instance = GetModuleHandleW(nullptr); - } - - // register class - WNDCLASSEXW wc = {0}; - wc.style = CS_OWNDC; - wc.lpfnWndProc = DefWindowProcW; - wc.lpszClassName = PAL_GL_CLASS; - wc.cbSize = sizeof(WNDCLASSEXW); - - // since we check every input carefully, the only error we can get is access - // denied - if (!RegisterClassExW(&wc)) { - return PAL_RESULT_ACCESS_DENIED; - } - - // create hidden window - s_Wgl.window = CreateWindowExW( - 0, - PAL_GL_CLASS, - L"Dummy Window", - 0, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - 0, - 0, - s_Wgl.instance, - 0); - - if (!s_Wgl.window) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_Gdi.handle = LoadLibraryA("gdi32.dll"); - s_Wgl.opengl = LoadLibraryA("opengl32.dll"); - if (!s_Gdi.handle || !s_Wgl.opengl) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // clang-format off - - // load gdi function pointers - s_Gdi.choosePixelFormat = (ChoosePixelFormatFn)GetProcAddress( - s_Gdi.handle, - "ChoosePixelFormat"); - - s_Gdi.setPixelFormat = (SetPixelFormatFn)GetProcAddress( - s_Gdi.handle, - "SetPixelFormat"); - - s_Gdi.getPixelFormat = (GetPixelFormatFn)GetProcAddress( - s_Gdi.handle, - "GetPixelFormat"); - - s_Gdi.describePixelFormat = (DescribePixelFormatFn)GetProcAddress( - s_Gdi.handle, - "DescribePixelFormat"); - - s_Gdi.swapBuffers = (SwapBuffersFn)GetProcAddress( - s_Gdi.handle, - "SwapBuffers"); - - // load wgl function pointers - s_Wgl.wglGetProcAddress = (wglGetProcAddressFn)GetProcAddress( - s_Wgl.opengl, - "wglGetProcAddress"); - - s_Wgl.wglCreateContext = (wglCreateContextFn)GetProcAddress( - s_Wgl.opengl, - "wglCreateContext"); - - s_Wgl.wglDeleteContext = (wglDeleteContextFn)GetProcAddress( - s_Wgl.opengl, - "wglDeleteContext"); - - s_Wgl.wglMakeCurrent = (wglMakeCurrentFn)GetProcAddress( - s_Wgl.opengl, - "wglMakeCurrent"); - - s_Wgl.wglShareLists = (wglShareListsFn)GetProcAddress( - s_Wgl.opengl, - "wglShareLists"); - - if (!s_Gdi.choosePixelFormat || - !s_Gdi.describePixelFormat || - !s_Gdi.swapBuffers || - !s_Gdi.setPixelFormat) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - if (!s_Wgl.wglGetProcAddress || - !s_Wgl.wglCreateContext || - !s_Wgl.wglDeleteContext || - !s_Wgl.wglMakeCurrent) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // clang-format on - - s_Wgl.hdc = GetDC(s_Wgl.window); - PIXELFORMATDESCRIPTOR pfd = {0}; - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; - pfd.cColorBits = 32; - pfd.cAlphaBits = 8; - pfd.iLayerType = PFD_MAIN_PLANE; - pfd.cDepthBits = 24; - pfd.cStencilBits = 8; - - Int32 pixelFormat = s_Gdi.choosePixelFormat(s_Wgl.hdc, &pfd); - s_Gdi.setPixelFormat(s_Wgl.hdc, pixelFormat, &pfd); - - s_Wgl.context = s_Wgl.wglCreateContext(s_Wgl.hdc); - if (!s_Wgl.wglMakeCurrent(s_Wgl.hdc, s_Wgl.context)) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // clang-format off - - // load wgl extension function pointers - s_Wgl.wglChoosePixelFormatARB = (wglChoosePixelFormatARBFn)s_Wgl.wglGetProcAddress( - "wglChoosePixelFormatARB"); - - s_Wgl.wglGetPixelFormatAttribivARB = (wglGetPixelFormatAttribivARBFn)s_Wgl.wglGetProcAddress( - "wglGetPixelFormatAttribivARB"); - - s_Wgl.wglCreateContextAttribsARB = (wglCreateContextAttribsARBFn)s_Wgl.wglGetProcAddress( - "wglCreateContextAttribsARB"); - - s_Wgl.wglSwapIntervalEXT = (wglSwapIntervalEXTFn)s_Wgl.wglGetProcAddress( - "wglSwapIntervalEXT"); - - s_Wgl.wglGetExtensionsStringARB = (wglGetExtensionsStringARBFn)s_Wgl.wglGetProcAddress( - "wglGetExtensionsStringARB"); - - s_Wgl.wglGetExtensionsStringEXT = (wglGetExtensionsStringEXTFn)s_Wgl.wglGetProcAddress( - "wglGetExtensionsStringEXT"); - - // load gl functions - s_Wgl.glGetString = (glGetStringFn)GetProcAddress( - s_Wgl.opengl, - "glGetString"); - - // clang-format on - - const char* version = (const char*)s_Wgl.glGetString(GL_VERSION); - if (version) { -#ifdef _MSC_VER - sscanf_s(version, "%d.%d", &s_Wgl.info.major, &s_Wgl.info.minor); -#else - sscanf(version, "%d.%d", &s_Wgl.info.major, &s_Wgl.info.minor); -#endif - } - - const char* renderer = (const char*)s_Wgl.glGetString(GL_RENDERER); - const char* vendor = (const char*)s_Wgl.glGetString(GL_VENDOR); - strcpy(s_Wgl.info.vendor, vendor); - strcpy(s_Wgl.info.version, version); - strcpy(s_Wgl.info.graphicsCard, renderer); - - // check available extensions - const char* extensions = nullptr; - if (s_Wgl.wglGetExtensionsStringARB) { - extensions = s_Wgl.wglGetExtensionsStringARB(s_Wgl.hdc); - - } else if (s_Wgl.wglGetExtensionsStringEXT) { - extensions = s_Wgl.wglGetExtensionsStringEXT(); - } - - if (extensions) { - // multisample - if (checkExtension("WGL_ARB_multisample", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; - } - // multisample - - // color space - if (checkExtension("WGL_ARB_framebuffer_sRGB", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; - } - - if (checkExtension("WGL_EXT_framebuffer_sRGB", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; - } - - if (checkExtension("WGL_EXT_colorspace", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; - } - // color space - - // create context - if (checkExtension("WGL_ARB_create_context", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_CREATE_CONTEXT; - } - // create context - - // create profile - if (checkExtension("WGL_ARB_create_context_profile", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE; - } - // create profile - - // create profile es2 - if (checkExtension("WGL_EXT_create_context_es2_profile", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; - } - // create profile es2 - - // robustness - if (checkExtension("WGL_ARB_create_context_robustness", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_ROBUSTNESS; - } - // robustness - - // no error - if (checkExtension("WGL_ARB_create_context_no_error", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_NO_ERROR; - } - // no error - - // swap control - if (checkExtension("WGL_EXT_swap_control", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; - } - // swap control - - // flush control - if (checkExtension("WGL_ARB_context_flush_control", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; - } - // flush control - - // pixel format - if (checkExtension("WGL_ARB_pixel_format", extensions)) { - s_Wgl.info.extensions |= PAL_GL_EXTENSION_PIXEL_FORMAT; - } - // pixel format - } - - s_Wgl.initialized = true; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palShutdownGL() -{ - if (!s_Wgl.initialized) { - return; - } - - s_Wgl.wglMakeCurrent(s_Wgl.hdc, nullptr); - s_Wgl.wglDeleteContext(s_Wgl.context); - ReleaseDC(s_Wgl.window, s_Wgl.hdc); - DestroyWindow(s_Wgl.window); - UnregisterClassW(PAL_GL_CLASS, s_Wgl.instance); - - FreeLibrary(s_Wgl.opengl); - FreeLibrary(s_Gdi.handle); - - memset(&s_Wgl, 0, sizeof(Wgl)); - s_Wgl.initialized = false; -} - -const PalGLInfo* PAL_CALL palGetGLInfo() -{ - if (!s_Wgl.initialized) { - return nullptr; - } - return &s_Wgl.info; -} - -PalResult PAL_CALL palEnumerateGLFBConfigs( - PalGLWindow* glWindow, - Int32* count, - PalGLFBConfig* configs) -{ - if (!s_Wgl.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!count) { - return PAL_RESULT_NULL_POINTER; - } - - if (count == 0 && configs) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - Int32 configCount = 0; - Int32 maxConfigCount = 0; - Int32 nativeCount = 0; - const Int32 configAttrib = WGL_NUMBER_PIXEL_FORMATS_ARB; - - if (configs) { - maxConfigCount = *count; - } - - // check if we support modern extention - if (s_Wgl.wglGetPixelFormatAttribivARB) { - // get framebuffer config with extensions - if (!s_Wgl.wglGetPixelFormatAttribivARB( - s_Wgl.hdc, - 0, - 0, - 1, - &configAttrib, - &nativeCount)) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // attributes we care about - Int32 attributes[] = { - WGL_SUPPORT_OPENGL_ARB, - WGL_DRAW_TO_WINDOW_ARB, - WGL_PIXEL_TYPE_ARB, - WGL_ACCELERATION_ARB, - WGL_RED_BITS_ARB, - WGL_GREEN_BITS_ARB, - WGL_BLUE_BITS_ARB, - WGL_ALPHA_BITS_ARB, - WGL_DEPTH_BITS_ARB, - WGL_STENCIL_BITS_ARB, - WGL_SAMPLES_ARB, - WGL_STEREO_ARB, - WGL_DOUBLE_BUFFER_ARB, - WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB}; - - Int32 values[sizeof(attributes) / sizeof(attributes[0])]; - for (Int32 i = 1; i <= nativeCount; i++) { - if (!s_Wgl.wglGetPixelFormatAttribivARB( - s_Wgl.hdc, - i, - 0, - sizeof(attributes) / sizeof(attributes[0]), - attributes, - values)) { - continue; - } - - // we index the values list in the same way as the arributes list - // so index 0 is WGL_SUPPORT_OPENGL_ARB and 1 is - // WGL_DRAW_TO_WINDOW_ARB - if (!values[0] || !values[1]) { - // WGL_SUPPORT_OPENGL_ARB and WGL_DRAW_TO_WINDOW_ARB support - continue; - } - - if (values[2] != WGL_TYPE_RGBA_ARB) { - // WGL_PIXEL_TYPE_ARB support - continue; - } - - if (values[3] == WGL_NO_ACCELERATION_ARB) { - continue; - } - - if (configs && configCount < maxConfigCount) { - PalGLFBConfig* config = &configs[configCount]; - config->index = i; - - config->redBits = values[4]; // WGL_RED_BITS_ARB - config->greenBits = values[5]; // WGL_GREEN_BITS_ARB - config->blueBits = values[6]; // WGL_BLUE_BITS_ARB - config->alphaBits = values[7]; // WGL_ALPHA_BITS_ARB - config->depthBits = values[8]; // WGL_DEPTH_BITS_ARB - config->stencilBits = values[9]; // WGL_STENCIL_BITS_ARB - config->samples = values[10]; // WGL_SAMPLES_ARB - - if (config->samples == 0) { - config->samples = 1; - } - - // WGL_STEREO_ARB - config->stereo = values[11]; - - // WGL_DOUBLE_BUFFER_ARB - config->doubleBuffer = values[12]; - - // WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB - config->sRGB = values[13]; - } - configCount++; - } - - } else { - // get pixel format with legacy pixel descriptor - nativeCount = s_Gdi.describePixelFormat(s_Wgl.hdc, 1, 0, nullptr); - - for (Int32 i = 1; i <= nativeCount; i++) { - PIXELFORMATDESCRIPTOR pfd; - if (!s_Gdi.describePixelFormat( - s_Wgl.hdc, - i, - sizeof(PIXELFORMATDESCRIPTOR), - &pfd)) { - continue; - } - - // filter for opengl pixel formats - if (!(pfd.dwFlags & PFD_SUPPORT_OPENGL) || - !(pfd.dwFlags & PFD_DRAW_TO_WINDOW)) { - continue; - } - - if (pfd.iPixelType != PFD_TYPE_RGBA) { - continue; - } - - if (!(pfd.dwFlags & PFD_GENERIC_ACCELERATED) && - (pfd.dwFlags & PFD_GENERIC_FORMAT)) { - continue; - } - - if (configs && configCount < maxConfigCount) { - PalGLFBConfig* config = &configs[configCount]; - config->index = i; - - config->redBits = pfd.cRedBits; - config->greenBits = pfd.cGreenBits; - config->blueBits = pfd.cBlueBits; - config->alphaBits = pfd.cAlphaBits; - config->depthBits = pfd.cDepthBits; - config->stencilBits = pfd.cStencilBits; - config->samples = 1; - - config->stereo = (pfd.dwFlags & PFD_STEREO) ? true : false; - config->sRGB = false; - - // clang-format off - config->doubleBuffer = (pfd.dwFlags & PFD_DOUBLEBUFFER) ? true : false; - // clang-format on - } - configCount++; - } - } - - if (!configs) { - *count = configCount; - } - return PAL_RESULT_SUCCESS; -} - -const PalGLFBConfig* PAL_CALL palGetClosestGLFBConfig( - PalGLFBConfig* configs, - Int32 count, - const PalGLFBConfig* desired) -{ - if (!s_Wgl.initialized) { - return nullptr; - } - - if (!configs || !desired) { - return nullptr; - } - - if (count == 0) { - return nullptr; - } - - Int32 score = 0; - Int32 bestScore = 0x7FFFFFFF; - PalGLFBConfig* best = nullptr; - for (Int32 i = 0; i < count; i++) { - PalGLFBConfig* tmp = &configs[i]; - - // filter out hard constraints - if (desired->doubleBuffer && !tmp->doubleBuffer) { - continue; - } - - if (desired->stereo && !tmp->stereo) { - continue; - } - - score = 0; - - // score color bits - score += abs(tmp->redBits - desired->redBits); - score += abs(tmp->greenBits - desired->greenBits); - score += abs(tmp->blueBits - desired->blueBits); - score += abs(tmp->alphaBits - desired->alphaBits); - score += abs(tmp->depthBits - desired->depthBits); - score += abs(tmp->stencilBits - desired->stencilBits); - - // score soft constraints - if (desired->samples != tmp->samples) { - score += 1000; - } - - if (desired->sRGB != tmp->sRGB) { - score += 500; - } - - if (score < bestScore) { - bestScore = score; - best = &configs[i]; - } - } - - return best; -} - -// ================================================== -// Context -// ================================================== - -PalResult PAL_CALL palCreateGLContext( - const PalGLContextCreateInfo* info, - PalGLContext** outContext) -{ - if (!s_Wgl.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!info || !outContext || (info && (!info->window || !info->fbConfig))) { - return PAL_RESULT_NULL_POINTER; - } - - // check support for requested features - if (info->profile != PAL_GL_PROFILE_NONE) { - if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->forward) { - if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_CREATE_CONTEXT)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { - if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_ROBUSTNESS)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->noError) { - if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_NO_ERROR)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { - if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL)) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - } - - // clang-format off - - // check version - bool valid = info->major < s_Wgl.info.major || - (info->major == s_Wgl.info.major && info->minor <= s_Wgl.info.minor); - // clang-format on - - if (!valid) { - return PAL_RESULT_INVALID_GL_VERSION; - } - - HDC hdc = GetDC((HWND)info->window->window); - if (!hdc) { - return PAL_RESULT_INVALID_GL_WINDOW; - } - - // check if the provided pixel format is the same as the windows - if (s_Gdi.getPixelFormat(hdc) != info->fbConfig->index) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - HGLRC share = nullptr; - if (info->shareContext) { - share = (HGLRC)info->shareContext; - } - - HGLRC context = nullptr; - if (s_Wgl.wglCreateContextAttribsARB) { - // create context with modern wgl functions - Int32 attribs[40]; - Int32 index = 0; - Int32 profile = 0; - Int32 flags = 0; - - // set context attributes - // the first element is the key and the second is the value - // set version - attribs[index++] = WGL_CONTEXT_MAJOR_VERSION_ARB; // key - attribs[index++] = info->major; // value - - attribs[index++] = WGL_CONTEXT_MINOR_VERSION_ARB; - attribs[index++] = info->minor; - - // set profile mask - if (info->profile != PAL_GL_PROFILE_NONE) { - attribs[index++] = WGL_CONTEXT_PROFILE_MASK_ARB; - - if (info->profile == PAL_GL_PROFILE_COMPATIBILITY) { - profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; - } else if (info->profile == PAL_GL_PROFILE_CORE) { - profile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; - } else { - profile = WGL_CONTEXT_ES2_PROFILE_BIT_EXT; - } - attribs[index++] = info->profile; - } - - // set forward flag - if (info->forward) { - flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; - } - - // set debug flag - if (info->debug) { - flags |= WGL_CONTEXT_DEBUG_BIT_ARB; - } - - // set robustness - if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { - flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; - attribs[index++] = WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; - - if (info->reset == PAL_GL_CONTEXT_RESET_LOSE_CONTEXT) { - attribs[index++] = WGL_LOSE_CONTEXT_ON_RESET_ARB; - - } else if (info->reset == PAL_GL_CONTEXT_RESET_NO_NOTIFICATION) { - attribs[index++] = WGL_NO_RESET_NOTIFICATION_ARB; - } - } - - // set no error - if (info->noError) { - attribs[index++] = WGL_CONTEXT_OPENGL_NO_ERROR_ARB; - attribs[index++] = true; - } - - // release - if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { - attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB; - attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB; - } - - if (flags) { - attribs[index++] = WGL_CONTEXT_FLAGS_ARB; - attribs[index++] = flags; - } - attribs[index++] = 0; - - context = s_Wgl.wglCreateContextAttribsARB(hdc, share, attribs); - if (!context) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_PROFILE_ARB) { - return PAL_RESULT_INVALID_GL_PROFILE; - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - } else { - // create context with legacy wgl functions - context = s_Wgl.wglCreateContext(hdc); - if (!context) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // share context - if (share) { - if (!s_Wgl.wglShareLists(share, context)) { - s_Wgl.wglDeleteContext(context); - ReleaseDC((HWND)info->window->window, hdc); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - } - - ReleaseDC((HWND)info->window->window, hdc); - *outContext = (PalGLContext*)context; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyGLContext(PalGLContext* context) -{ - if (!s_Wgl.initialized || !context) { - return; - } - s_Wgl.wglDeleteContext((HGLRC)context); -} - -PalResult PAL_CALL palMakeContextCurrent( - PalGLWindow* glWindow, - PalGLContext* context) -{ - if (!s_Wgl.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if ((!glWindow && context) || (glWindow && !context)) { - return PAL_RESULT_NULL_POINTER; - } - - if (context && glWindow) { - // get hdc - HDC hdc = GetDC((HWND)glWindow->window); - if (!hdc) { - return PAL_RESULT_INVALID_GL_WINDOW; - } - - if (!s_Wgl.wglMakeCurrent(hdc, (HGLRC)context)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_GL_CONTEXT; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - ReleaseDC((HWND)glWindow->window, hdc); - - } else if (!context && !glWindow) { - s_Wgl.wglMakeCurrent(nullptr, nullptr); - } - - return PAL_RESULT_SUCCESS; -} - -void* PAL_CALL palGLGetProcAddress(const char* name) -{ - if (!s_Wgl.initialized) { - return nullptr; - } - - void* proc = s_Wgl.wglGetProcAddress(name); - if (!proc) { - proc = (void*)GetProcAddress(s_Wgl.opengl, name); - } - return proc; -} - -PalResult PAL_CALL palSwapBuffers( - PalGLWindow* glWindow, - PalGLContext* context) -{ - if (!s_Wgl.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!context || !glWindow) { - return PAL_RESULT_NULL_POINTER; - } - - // get hdc - HDC hdc = GetDC((HWND)glWindow->window); - if (!hdc) { - return PAL_RESULT_INVALID_GL_WINDOW; - } - - if (!s_Gdi.swapBuffers(hdc)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_PIXEL_FORMAT) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - ReleaseDC((HWND)glWindow->window, hdc); - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetSwapInterval(Int32 interval) -{ - if (!s_Wgl.initialized) { - return PAL_RESULT_GL_NOT_INITIALIZED; - } - - if (!s_Wgl.wglSwapIntervalEXT) { - return PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED; - } - - s_Wgl.wglSwapIntervalEXT(interval); - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palGLSetInstance(void* instance) -{ - s_Wgl.instance = instance; -} - -const char* PAL_CALL palGLGetBackend() -{ - if (!s_Wgl.initialized) { - return nullptr; - } - return "wgl"; -} \ No newline at end of file diff --git a/src/opengl/wgl/pal_context_wgl.c b/src/opengl/wgl/pal_context_wgl.c new file mode 100644 index 00000000..e4d1acf1 --- /dev/null +++ b/src/opengl/wgl/pal_context_wgl.c @@ -0,0 +1,264 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_wgl.h" + +PalResult wglCreateGLContext( + const PalGLContextCreateInfo* info, + PalGLContext** outContext) +{ + // check support for requested features + if (info->profile != PAL_GL_PROFILE_NONE) { + if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->forward) { + if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_CREATE_CONTEXT)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_ROBUSTNESS)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->noError) { + if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_NO_ERROR)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + if (!(s_Wgl.info.extensions & PAL_GL_EXTENSION_FLUSH_CONTROL)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + // check version + // clang-format off + PalBool valid = info->major < s_Wgl.info.major || + (info->major == s_Wgl.info.major && info->minor <= s_Wgl.info.minor); + // clang-format on + + if (!valid) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + HDC hdc = GetDC((HWND)info->window->window); + if (!hdc) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // check if the provided pixel format is the same as the window + if (s_Gdi.getPixelFormat(hdc) != info->fbConfig->index) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + HGLRC share = nullptr; + if (info->shareContext) { + share = (HGLRC)info->shareContext; + } + + HGLRC context = nullptr; + if (s_Wgl.createContextAttribsARB) { + // create context with modern wgl functions + int32_t attribs[40]; + int32_t index = 0; + int32_t profile = 0; + int32_t flags = 0; + + // set context attributes + // the first element is the key and the second is the value + // set version + attribs[index++] = WGL_CONTEXT_MAJOR_VERSION_ARB; // key + attribs[index++] = info->major; // value + + attribs[index++] = WGL_CONTEXT_MINOR_VERSION_ARB; + attribs[index++] = info->minor; + + // set profile mask + if (info->profile != PAL_GL_PROFILE_NONE) { + attribs[index++] = WGL_CONTEXT_PROFILE_MASK_ARB; + + if (info->profile == PAL_GL_PROFILE_COMPATIBILITY) { + profile = WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB; + } else if (info->profile == PAL_GL_PROFILE_CORE) { + profile = WGL_CONTEXT_CORE_PROFILE_BIT_ARB; + } else { + profile = WGL_CONTEXT_ES2_PROFILE_BIT_EXT; + } + attribs[index++] = info->profile; + } + + // set forward flag + if (info->forward) { + flags |= WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB; + } + + // set debug flag + if (info->debug) { + flags |= WGL_CONTEXT_DEBUG_BIT_ARB; + } + + // set robustness + if (info->reset != PAL_GL_CONTEXT_RESET_NONE) { + flags |= WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB; + attribs[index++] = WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB; + + if (info->reset == PAL_GL_CONTEXT_RESET_LOSE_CONTEXT) { + attribs[index++] = WGL_LOSE_CONTEXT_ON_RESET_ARB; + + } else if (info->reset == PAL_GL_CONTEXT_RESET_NO_NOTIFICATION) { + attribs[index++] = WGL_NO_RESET_NOTIFICATION_ARB; + } + } + + // set no error + if (info->noError) { + attribs[index++] = WGL_CONTEXT_OPENGL_NO_ERROR_ARB; + attribs[index++] = PAL_TRUE; + } + + // release + if (info->release != PAL_GL_RELEASE_BEHAVIOR_NONE) { + attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_ARB; + attribs[index++] = WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB; + } + + if (flags) { + attribs[index++] = WGL_CONTEXT_FLAGS_ARB; + attribs[index++] = flags; + } + attribs[index++] = 0; + + context = s_Wgl.createContextAttribsARB(hdc, share, attribs); + if (!context) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_PROFILE_ARB) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + error); + + } else { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + error); + } + } + + } else { + // create context with legacy wgl functions + context = s_Wgl.createContext(hdc); + if (!context) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // share context + if (share) { + if (!s_Wgl.shareLists(share, context)) { + s_Wgl.deleteContext(context); + ReleaseDC((HWND)info->window->window, hdc); + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + } + } + + ReleaseDC((HWND)info->window->window, hdc); + *outContext = (PalGLContext*)context; + return PAL_RESULT_SUCCESS; +} + +void wglDestroyGLContext(PalGLContext* context) +{ + s_Wgl.deleteContext((HGLRC)context); +} + +PalResult wglMakeContextCurrent( + PalGLWindow* glWindow, + PalGLContext* context) +{ + if (context && glWindow) { + // get hdc + HDC hdc = GetDC((HWND)glWindow->window); + if (!hdc) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + if (!s_Wgl.makeCurrent(hdc, (HGLRC)context)) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_HANDLE) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + error); + + } else { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + error); + } + } + ReleaseDC((HWND)glWindow->window, hdc); + + } else if (!context && !glWindow) { + s_Wgl.makeCurrent(nullptr, nullptr); + } + + return PAL_RESULT_SUCCESS; +} + +PalResult wglSwapBuffers( + PalGLWindow* glWindow, + PalGLContext* context) +{ + // get hdc + HDC hdc = GetDC((HWND)glWindow->window); + if (!hdc) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + if (!s_Gdi.swapBuffers(hdc)) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_PIXEL_FORMAT) { + return palMakeResult(PAL_RESULT_CODE_INVALID_ARGUMENT, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + + ReleaseDC((HWND)glWindow->window, hdc); + return PAL_RESULT_SUCCESS; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/opengl/wgl/pal_wgl.c b/src/opengl/wgl/pal_wgl.c new file mode 100644 index 00000000..2af5b4a3 --- /dev/null +++ b/src/opengl/wgl/pal_wgl.c @@ -0,0 +1,467 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_wgl.h" +#include "opengl/pal_opengl_shared.h" +#include +#include + +Gdi s_Gdi = {0}; +Wgl s_Wgl = {0}; +static PalBool s_SupportedAPIs[2] = {0}; + +PalResult wglInitGL( + PalGLAPI api, + void* instance, + const PalAllocator* allocator) +{ + if (api != PAL_GL_API_OPENGL) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + // register class + s_Wgl.instance = instance; + WNDCLASSEXW wc = {0}; + wc.style = CS_OWNDC; + wc.lpfnWndProc = DefWindowProcW; + wc.lpszClassName = PAL_GL_CLASS; + wc.cbSize = sizeof(WNDCLASSEXW); + + // since we check every input carefully, the only error we can get is access + // denied + if (!RegisterClassExW(&wc)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // create hidden window + s_Wgl.window = CreateWindowExW( + 0, + PAL_GL_CLASS, + L"Dummy Window", + 0, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + 0, + 0, + s_Wgl.instance, + 0); + + if (!s_Wgl.window) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + s_Gdi.handle = LoadLibraryA("gdi32.dll"); + s_Wgl.opengl = LoadLibraryA("opengl32.dll"); + if (!s_Gdi.handle || !s_Wgl.opengl) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // clang-format off + // load gdi function pointers + s_Gdi.choosePixelFormat = (ChoosePixelFormatFn)GetProcAddress( + s_Gdi.handle, + "ChoosePixelFormat"); + + s_Gdi.setPixelFormat = (SetPixelFormatFn)GetProcAddress( + s_Gdi.handle, + "SetPixelFormat"); + + s_Gdi.getPixelFormat = (GetPixelFormatFn)GetProcAddress( + s_Gdi.handle, + "GetPixelFormat"); + + s_Gdi.describePixelFormat = (DescribePixelFormatFn)GetProcAddress( + s_Gdi.handle, + "DescribePixelFormat"); + + s_Gdi.swapBuffers = (SwapBuffersFn)GetProcAddress( + s_Gdi.handle, + "SwapBuffers"); + + // load wgl function pointers + s_Wgl.getProcAddress = (wglGetProcAddressFn)GetProcAddress( + s_Wgl.opengl, + "wglGetProcAddress"); + + s_Wgl.createContext = (wglCreateContextFn)GetProcAddress( + s_Wgl.opengl, + "wglCreateContext"); + + s_Wgl.deleteContext = (wglDeleteContextFn)GetProcAddress( + s_Wgl.opengl, + "wglDeleteContext"); + + s_Wgl.makeCurrent = (wglMakeCurrentFn)GetProcAddress( + s_Wgl.opengl, + "wglMakeCurrent"); + + s_Wgl.shareLists = (wglShareListsFn)GetProcAddress( + s_Wgl.opengl, + "wglShareLists"); + + if (!s_Gdi.choosePixelFormat || + !s_Gdi.describePixelFormat || + !s_Gdi.swapBuffers || + !s_Gdi.setPixelFormat) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + if (!s_Wgl.getProcAddress || + !s_Wgl.createContext || + !s_Wgl.deleteContext || + !s_Wgl.makeCurrent) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + // clang-format on + + s_Wgl.hdc = GetDC(s_Wgl.window); + PIXELFORMATDESCRIPTOR pfd = {0}; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + + int32_t pixelFormat = s_Gdi.choosePixelFormat(s_Wgl.hdc, &pfd); + s_Gdi.setPixelFormat(s_Wgl.hdc, pixelFormat, &pfd); + s_Wgl.context = s_Wgl.createContext(s_Wgl.hdc); + + if (!s_Wgl.makeCurrent(s_Wgl.hdc, s_Wgl.context)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // clang-format off + // load wgl extension function pointers + s_Wgl.choosePixelFormatARB = (wglChoosePixelFormatARBFn)s_Wgl.getProcAddress( + "wglChoosePixelFormatARB"); + + s_Wgl.getPixelFormatAttribivARB = (wglGetPixelFormatAttribivARBFn)s_Wgl.getProcAddress( + "wglGetPixelFormatAttribivARB"); + + s_Wgl.createContextAttribsARB = (wglCreateContextAttribsARBFn)s_Wgl.getProcAddress( + "wglCreateContextAttribsARB"); + + s_Wgl.swapIntervalEXT = (wglSwapIntervalEXTFn)s_Wgl.getProcAddress( + "wglSwapIntervalEXT"); + + s_Wgl.getExtensionsStringARB = (wglGetExtensionsStringARBFn)s_Wgl.getProcAddress( + "wglGetExtensionsStringARB"); + + s_Wgl.getExtensionsStringEXT = (wglGetExtensionsStringEXTFn)s_Wgl.getProcAddress( + "wglGetExtensionsStringEXT"); + + // load gl functions + s_Wgl.glGetString = (glGetStringFn)GetProcAddress( + s_Wgl.opengl, + "glGetString"); + // clang-format on + + const char* version = (const char*)s_Wgl.glGetString(GL_VERSION); + if (version) { +#ifdef _MSC_VER + sscanf_s(version, "%d.%d", &s_Wgl.info.major, &s_Wgl.info.minor); +#else + sscanf(version, "%d.%d", &s_Wgl.info.major, &s_Wgl.info.minor); +#endif + } + + const char* renderer = (const char*)s_Wgl.glGetString(GL_RENDERER); + const char* vendor = (const char*)s_Wgl.glGetString(GL_VENDOR); + strcpy(s_Wgl.info.vendor, vendor); + strcpy(s_Wgl.info.version, version); + strcpy(s_Wgl.info.graphicsCard, renderer); + + // check available extensions + const char* extensions = nullptr; + if (s_Wgl.getExtensionsStringARB) { + extensions = s_Wgl.getExtensionsStringARB(s_Wgl.hdc); + + } else if (s_Wgl.getExtensionsStringEXT) { + extensions = s_Wgl.getExtensionsStringEXT(); + } + + if (extensions) { + // multisample + if (checkString("WGL_ARB_multisample", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_MULTISAMPLE; + } + // multisample + + // color space + if (checkString("WGL_ARB_framebuffer_sRGB", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; + } + + if (checkString("WGL_EXT_framebuffer_sRGB", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; + } + + if (checkString("WGL_EXT_colorspace", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_COLORSPACE_SRGB; + } + // color space + + // create context + if (checkString("WGL_ARB_create_context", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_CREATE_CONTEXT; + } + // create context + + // create profile + if (checkString("WGL_ARB_create_context_profile", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE; + } + // create profile + + // create profile es2 + if (checkString("WGL_EXT_create_context_es2_profile", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_CONTEXT_PROFILE_ES2; + } + // create profile es2 + + // robustness + if (checkString("WGL_ARB_create_context_robustness", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_ROBUSTNESS; + } + // robustness + + // no error + if (checkString("WGL_ARB_create_context_no_error", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_NO_ERROR; + } + // no error + + // swap control + if (checkString("WGL_EXT_swap_control", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_SWAP_CONTROL; + } + // swap control + + // flush control + if (checkString("WGL_ARB_context_flush_control", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_FLUSH_CONTROL; + } + // flush control + + // pixel format + if (checkString("WGL_ARB_pixel_format", extensions)) { + s_Wgl.info.extensions |= PAL_GL_EXTENSION_PIXEL_FORMAT; + } + // pixel format + } + + s_Wgl.info.api = PAL_GL_API_OPENGL; + s_Wgl.info.backend = PAL_GL_BACKEND_WGL; + return PAL_RESULT_SUCCESS; +} + +void wglShutdownGL() +{ + s_Wgl.makeCurrent(s_Wgl.hdc, nullptr); + s_Wgl.deleteContext(s_Wgl.context); + ReleaseDC(s_Wgl.window, s_Wgl.hdc); + DestroyWindow(s_Wgl.window); + UnregisterClassW(PAL_GL_CLASS, s_Wgl.instance); + + FreeLibrary(s_Wgl.opengl); + FreeLibrary(s_Gdi.handle); + + memset(&s_Wgl, 0, sizeof(Wgl)); +} + +const PalGLInfo* wglGetGLInfo() +{ + return &s_Wgl.info; +} + +PalResult wglEnumerateGLFBConfigs( + uint32_t* count, + PalGLFBConfig* configs) +{ + int32_t configCount = 0; + int32_t maxConfigCount = 0; + int32_t nativeCount = 0; + const int32_t configAttrib = WGL_NUMBER_PIXEL_FORMATS_ARB; + + if (configs) { + maxConfigCount = *count; + } + + // check if we support modern extention + if (s_Wgl.getPixelFormatAttribivARB) { + // get framebuffer config with extensions + if (!s_Wgl.getPixelFormatAttribivARB(s_Wgl.hdc, 0, 0, 1, &configAttrib, &nativeCount)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // attributes we care about + int32_t attributes[] = { + WGL_SUPPORT_OPENGL_ARB, + WGL_DRAW_TO_WINDOW_ARB, + WGL_PIXEL_TYPE_ARB, + WGL_ACCELERATION_ARB, + WGL_RED_BITS_ARB, + WGL_GREEN_BITS_ARB, + WGL_BLUE_BITS_ARB, + WGL_ALPHA_BITS_ARB, + WGL_DEPTH_BITS_ARB, + WGL_STENCIL_BITS_ARB, + WGL_SAMPLES_ARB, + WGL_STEREO_ARB, + WGL_DOUBLE_BUFFER_ARB, + WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB}; + + int32_t values[sizeof(attributes) / sizeof(attributes[0])]; + for (int32_t i = 1; i <= nativeCount; i++) { + if (!s_Wgl.getPixelFormatAttribivARB( + s_Wgl.hdc, + i, + 0, + sizeof(attributes) / sizeof(attributes[0]), + attributes, + values)) { + continue; + } + + // we index the values list in the same way as the arributes list + // so index 0 is WGL_SUPPORT_OPENGL_ARB and 1 is + // WGL_DRAW_TO_WINDOW_ARB + if (!values[0] || !values[1]) { + // WGL_SUPPORT_OPENGL_ARB and WGL_DRAW_TO_WINDOW_ARB support + continue; + } + + if (values[2] != WGL_TYPE_RGBA_ARB) { + // WGL_PIXEL_TYPE_ARB support + continue; + } + + if (values[3] == WGL_NO_ACCELERATION_ARB) { + continue; + } + + if (configs && configCount < maxConfigCount) { + PalGLFBConfig* config = &configs[configCount]; + config->index = i; + + config->redBits = values[4]; // WGL_RED_BITS_ARB + config->greenBits = values[5]; // WGL_GREEN_BITS_ARB + config->blueBits = values[6]; // WGL_BLUE_BITS_ARB + config->alphaBits = values[7]; // WGL_ALPHA_BITS_ARB + config->depthBits = values[8]; // WGL_DEPTH_BITS_ARB + config->stencilBits = values[9]; // WGL_STENCIL_BITS_ARB + config->samples = values[10]; // WGL_SAMPLES_ARB + + if (config->samples == 0) { + config->samples = 1; + } + + // WGL_STEREO_ARB + config->stereo = values[11]; + + // WGL_DOUBLE_BUFFER_ARB + config->doubleBuffer = values[12]; + + // WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB + config->sRGB = values[13]; + } + configCount++; + } + + } else { + // get pixel format with legacy pixel descriptor + nativeCount = s_Gdi.describePixelFormat(s_Wgl.hdc, 1, 0, nullptr); + + for (int32_t i = 1; i <= nativeCount; i++) { + PIXELFORMATDESCRIPTOR pfd; + if (!s_Gdi.describePixelFormat(s_Wgl.hdc, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { + continue; + } + + // filter for opengl pixel formats + if (!(pfd.dwFlags & PFD_SUPPORT_OPENGL) || !(pfd.dwFlags & PFD_DRAW_TO_WINDOW)) { + continue; + } + + if (pfd.iPixelType != PFD_TYPE_RGBA) { + continue; + } + + if (!(pfd.dwFlags & PFD_GENERIC_ACCELERATED) && (pfd.dwFlags & PFD_GENERIC_FORMAT)) { + continue; + } + + if (configs && configCount < maxConfigCount) { + PalGLFBConfig* config = &configs[configCount]; + config->index = i; + + config->redBits = pfd.cRedBits; + config->greenBits = pfd.cGreenBits; + config->blueBits = pfd.cBlueBits; + config->alphaBits = pfd.cAlphaBits; + config->depthBits = pfd.cDepthBits; + config->stencilBits = pfd.cStencilBits; + config->samples = 1; + + config->stereo = (pfd.dwFlags & PFD_STEREO) ? PAL_TRUE : PAL_FALSE; + config->sRGB = PAL_FALSE; + config->doubleBuffer = (pfd.dwFlags & PFD_DOUBLEBUFFER) ? PAL_TRUE : PAL_FALSE; + } + configCount++; + } + } + + if (!configs) { + *count = configCount; + } + return PAL_RESULT_SUCCESS; +} + +void* wglGetGLProcAddress(const char* name) +{ + void* proc = s_Wgl.getProcAddress(name); + if (!proc) { + proc = (void*)GetProcAddress(s_Wgl.opengl, name); + } + return proc; +} + +void wglSetSwapInterval(int32_t interval) +{ + s_Wgl.swapIntervalEXT(interval); +} + +const PalBool* wglGetSupportedGLAPIs(void* instance) +{ + s_SupportedAPIs[PAL_GL_API_OPENGL] = PAL_TRUE; + s_SupportedAPIs[PAL_GL_API_OPENGL_ES] = PAL_FALSE; + return s_SupportedAPIs; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/opengl/wgl/pal_wgl.h b/src/opengl/wgl/pal_wgl.h new file mode 100644 index 00000000..f6df70e0 --- /dev/null +++ b/src/opengl/wgl/pal_wgl.h @@ -0,0 +1,184 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_WGL_H +#define _PAL_WGL_H + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_opengl.h" +#include + +#define PAL_GL_CLASS L"PALGLClass" + +// check to see if this is not defined yet +#ifndef GL_VENDOR +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 +#endif // GL_VENDOR + +#ifndef WGL_NUMBER_PIXEL_FORMATS_ARB +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_ALPHA_BITS_ARB 0x201b + +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_STEREO_ARB 0x2012 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_SAMPLES_ARB 0x2042 +#define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB 0x20a9 +#define WGL_TYPE_RGBA_ARB 0x202b +#define WGL_NO_ACCELERATION_ARB 0x2025 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x00000001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 +#define WGL_CONTEXT_ROBUST_ACCESS_BIT_ARB 0x00000004 +#define WGL_LOSE_CONTEXT_ON_RESET_ARB 0x8252 +#define WGL_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 0x8256 +#define WGL_NO_RESET_NOTIFICATION_ARB 0x8261 +#define WGL_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_ARB 0x2097 +#define WGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 + +#define ERROR_INVALID_PROFILE_ARB 0x2096 +#endif // WGL_NUMBER_PIXEL_FORMATS_ARB + +typedef unsigned int GLenum; +typedef unsigned char GLubyte; + +// gdi functions +typedef int(WINAPI* ChoosePixelFormatFn)( + HDC, + CONST PIXELFORMATDESCRIPTOR*); + +typedef BOOL(WINAPI* SetPixelFormatFn)( + HDC, + int, + CONST PIXELFORMATDESCRIPTOR*); + +typedef int(WINAPI* DescribePixelFormatFn)( + HDC, + int, + UINT, + LPPIXELFORMATDESCRIPTOR); + +typedef int(WINAPI* GetPixelFormatFn)(HDC); + +typedef BOOL(WINAPI* SwapBuffersFn)(HDC); + +// wgl functions +typedef PROC(WINAPI* wglGetProcAddressFn)(LPCSTR); + +typedef HGLRC(WINAPI* wglCreateContextFn)(HDC); + +typedef BOOL(WINAPI* wglDeleteContextFn)(HGLRC); + +typedef BOOL(WINAPI* wglShareListsFn)( + HGLRC, + HGLRC); + +typedef BOOL(WINAPI* wglMakeCurrentFn)( + HDC, + HGLRC); + +// gl functions +typedef const GLubyte*(WINAPI* glGetStringFn)(GLenum); + +// extensions +typedef BOOL(WINAPI* wglChoosePixelFormatARBFn)( + HDC, + const int*, + const FLOAT*, + UINT, + int*, + UINT*); + +typedef BOOL(WINAPI* wglGetPixelFormatAttribivARBFn)( + HDC, + int, + int, + UINT, + const int*, + int*); + +typedef HGLRC(WINAPI* wglCreateContextAttribsARBFn)( + HDC, + HGLRC, + const int*); + +typedef BOOL(WINAPI* wglSwapIntervalEXTFn)(int); + +typedef const char*(WINAPI* wglGetExtensionsStringEXTFn)(); + +typedef const char*(WINAPI* wglGetExtensionsStringARBFn)(HDC); + +typedef struct { + SetPixelFormatFn setPixelFormat; + DescribePixelFormatFn describePixelFormat; + ChoosePixelFormatFn choosePixelFormat; + GetPixelFormatFn getPixelFormat; + SwapBuffersFn swapBuffers; + HINSTANCE handle; +} Gdi; + +typedef struct { + wglGetProcAddressFn getProcAddress; + wglCreateContextFn createContext; + wglDeleteContextFn deleteContext; + wglMakeCurrentFn makeCurrent; + wglShareListsFn shareLists; + glGetStringFn glGetString; + + wglCreateContextAttribsARBFn createContextAttribsARB; + wglChoosePixelFormatARBFn choosePixelFormatARB; + wglSwapIntervalEXTFn swapIntervalEXT; + wglGetExtensionsStringEXTFn getExtensionsStringEXT; + wglGetExtensionsStringARBFn getExtensionsStringARB; + wglGetPixelFormatAttribivARBFn getPixelFormatAttribivARB; + + const PalAllocator* allocator; + HINSTANCE opengl; + HINSTANCE instance; + HWND window; + HDC hdc; + HGLRC context; + PalGLInfo info; +} Wgl; + +extern Gdi s_Gdi; +extern Wgl s_Wgl; + +#endif // _WIN32 +#endif // _PAL_WGL_H \ No newline at end of file diff --git a/src/pal_core.c b/src/pal_core.c deleted file mode 100644 index f9b5de85..00000000 --- a/src/pal_core.c +++ /dev/null @@ -1,474 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#ifdef __linux__ -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200112L -#include -#include -#include -#include -#include -#include -#endif // __linux__ - -#include "pal/pal_core.h" - -#ifdef _WIN32 -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// set unicode -#ifndef UNICODE -#define UNICODE -#endif // UNICODE - -#include -#endif // _WIN32 - -#if defined(_MSC_VER) || defined(__MINGW32__) -#include -#endif // _MSC_VER - -#include -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define PAL_DEFAULT_ALIGNMENT 16 -#define PAL_VERSION_MAJOR 1 -#define PAL_VERSION_MINOR 3 -#define PAL_VERSION_BUILD 0 -#define PAL_VERSION_STRING "1.3.0" -#define PAL_LOG_MSG_SIZE 4096 - -#ifdef _WIN32 -static volatile LONG s_TlsID = 0; -#elif defined(__linux__) -pthread_key_t s_TLSID = 0; -static pthread_once_t s_TLSCreation = PTHREAD_ONCE_INIT; -#endif // _WIN32 - -typedef struct { - char tmp[PAL_LOG_MSG_SIZE]; - char buffer[PAL_LOG_MSG_SIZE]; - char platformResultDesc[PAL_LOG_MSG_SIZE]; - wchar_t wideBuffer[PAL_LOG_MSG_SIZE]; - bool isLogging; -} LogTLSData; - -// ================================================== -// Internal API -// ================================================== - -static void destroyTlsData(void* data); - -#ifdef __linux__ -void createTLSID() -{ - if (pthread_key_create(&s_TLSID, destroyTlsData) != 0) { - // FIXME: Use a global log buffer with a mutex - return; - } -} -#endif // __linux__ - -static inline void* alignedAlloc( - Uint64 size, - Uint64 alignment) -{ -#if defined(_MSC_VER) || defined(__MINGW32__) - return _aligned_malloc(size, alignment); -#elif defined(_ISOC11_SOURCE) || \ - defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - return aligned_alloc(alignment, size); -#else - void* ptr = nullptr; - posix_memalign(&ptr, alignment, size); - return ptr; -#endif // _MSC_VER -} - -static inline void alignedFree(void* ptr) -{ -#if defined(_MSC_VER) || defined(__MINGW32__) - _aligned_free(ptr); -#else - free(ptr); -#endif // _MSC_VER -} - -static void destroyTlsData(void* data) -{ - LogTLSData* tlsData = data; - if (tlsData) { - palFree(nullptr, tlsData); - } -} - -static inline LogTLSData* getLogTlsData() -{ -#ifdef _WIN32 - LogTLSData* data = FlsGetValue((DWORD)s_TlsID); -#elif defined(__linux__) - LogTLSData* data = pthread_getspecific(s_TLSID); -#endif // _WIN32 - - if (!data) { - data = palAllocate(nullptr, sizeof(LogTLSData), 0); - memset(data, 0, sizeof(LogTLSData)); - - // create TLS if it has not been created -#ifdef _WIN32 - if (s_TlsID == 0) { - DWORD TLSIndex = FlsAlloc(destroyTlsData); - if (TLSIndex == TLS_OUT_OF_INDEXES) { - // FIXME: Use a global log buffer with a mutex - return nullptr; - } else { - // update the TLS using atomic operations to avoid thread race - LONG prev = InterlockedCompareExchange( - (volatile LONG*)&s_TlsID, - (LONG)TLSIndex, - 0); - - if (prev != 0) { - // Another thread has already set this, - // destroy the tls index - FlsFree(TLSIndex); - } - } - } - FlsSetValue(s_TlsID, data); -#elif defined(__linux__) - pthread_once(&s_TLSCreation, createTLSID); - pthread_setspecific(s_TLSID, data); -#endif // _WIN32 - } - return data; -} - -static inline void updateLogTlsData(LogTLSData* data) -{ -#ifdef _WIN32 - FlsSetValue(s_TlsID, data); -#elif defined(__linux__) - pthread_setspecific(s_TLSID, data); -#endif // _WIN32 -} - -static inline void formatArgs( - const char* fmt, - va_list argsList, - char* buffer) -{ - va_list argsListCopy; - va_copy(argsListCopy, argsList); - int len = vsnprintf(nullptr, 0, fmt, argsListCopy); - va_end(argsListCopy); - - va_copy(argsListCopy, argsList); - vsnprintf(buffer, len + 1, fmt, argsListCopy); - va_end(argsListCopy); - buffer[len] = 0; -} - -static inline void format( - char* buffer, - const char* fmt, - ...) -{ - va_list argPtr; - va_start(argPtr, fmt); - formatArgs(fmt, argPtr, buffer); - va_end(argPtr); -} - -static inline void writeToConsole(LogTLSData* data) -{ -#ifdef _WIN32 - HANDLE console = GetStdHandle(STD_ERROR_HANDLE); - int len = MultiByteToWideChar(CP_UTF8, 0, data->buffer, -1, nullptr, 0); - if (!len) { - return; - } - - MultiByteToWideChar(CP_UTF8, 0, data->buffer, -1, data->wideBuffer, len); - if (console) { - WriteConsoleW(console, data->wideBuffer, (DWORD)len - 1, NULL, 0); - } else { - OutputDebugStringW(data->wideBuffer); - } -#elif defined(__linux__) - fprintf(stdout, "%s", data->buffer); - fflush(stdout); -#endif // _WIN32 -} - -void palSetLastPlatformError(Uint32 e) -{ - LogTLSData* data = getLogTlsData(); - memset(data->platformResultDesc, 0, PAL_LOG_MSG_SIZE); - - if (e == 0) { - return; - } - -#ifdef __linux__ -#if defined(__GLIBC__) - char* ret = strerror_r(e, data->platformResultDesc, PAL_LOG_MSG_SIZE); - if (ret != data->platformResultDesc) { - snprintf(data->platformResultDesc, PAL_LOG_MSG_SIZE, "%s", ret); - } -#else - strerror_r(e, data->platformResultDesc, PAL_LOG_MSG_SIZE); -#endif // __GLIBC__ -#else - FormatMessageA( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, - e, - 0, - data->platformResultDesc, - PAL_LOG_MSG_SIZE, - nullptr); -#endif // __linux__ -} - -// ================================================== -// Public API -// ================================================== - -PalVersion PAL_CALL palGetVersion() -{ - return (PalVersion){.major = PAL_VERSION_MAJOR, - .minor = PAL_VERSION_MINOR, - .build = PAL_VERSION_BUILD}; -} - -const char* PAL_CALL palGetVersionString() -{ - return PAL_VERSION_STRING; -} - -const char* PAL_CALL palFormatResult(PalResult result) -{ - switch (result) { - case PAL_RESULT_SUCCESS: - return "Success"; - - case PAL_RESULT_NULL_POINTER: - return "Null pointer"; - - case PAL_RESULT_INVALID_ARGUMENT: - return "Invalid argument"; - - case PAL_RESULT_OUT_OF_MEMORY: - return "Out of memory"; - - case PAL_RESULT_PLATFORM_FAILURE: { - LogTLSData* data = getLogTlsData(); - if (!data) { - return "Platform error"; - } else if (data && data->platformResultDesc[0] == 0) { - return "Platform error"; - } else { - return data->platformResultDesc; - } - } - - case PAL_RESULT_INVALID_ALLOCATOR: - return "Invalif allocator"; - - case PAL_RESULT_ACCESS_DENIED: - return "Access denied"; - - case PAL_RESULT_TIMEOUT: - return "Timeout expired"; - - case PAL_RESULT_INSUFFICIENT_BUFFER: - return "Insufficient buffer"; - - // thread - case PAL_RESULT_INVALID_THREAD: - return "Invalid thread"; - - case PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED: - return "Unsupported thread feature"; - - // video - case PAL_RESULT_VIDEO_NOT_INITIALIZED: - return "Video system not initialized"; - - case PAL_RESULT_INVALID_MONITOR: - return "Invalid monitor"; - - case PAL_RESULT_INVALID_MONITOR_MODE: - return "Invalid monitor display mode"; - - case PAL_RESULT_INVALID_WINDOW: - return "Invalid window"; - - case PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED: - return "Unsupported video feature"; - - case PAL_RESULT_INVALID_KEYCODE: - return "Invalid keycode"; - - case PAL_RESULT_INVALID_SCANCODE: - return "Invalid scancode"; - - case PAL_RESULT_INVALID_MOUSE_BUTTON: - return "Invalid mouse button"; - - case PAL_RESULT_INVALID_ORIENTATION: - return "Invalid orientation"; - - // opengl - case PAL_RESULT_GL_NOT_INITIALIZED: - return "Opengl system not initialized"; - - case PAL_RESULT_INVALID_GL_WINDOW: - return "Invalid opengl window"; - - case PAL_RESULT_GL_EXTENSION_NOT_SUPPORTED: - return "Unsupported opengl extension"; - - case PAL_RESULT_INVALID_GL_FBCONFIG: - return "Invalid opengl framebuffer"; - - case PAL_RESULT_INVALID_GL_VERSION: - return "Unsupported opengl version"; - - case PAL_RESULT_INVALID_GL_PROFILE: - return "Unsupported opengl profile"; - - case PAL_RESULT_INVALID_GL_CONTEXT: - return "Invalid opengl context"; - - case PAL_RESULT_INVALID_FBCONFIG_BACKEND: - return "Invalid FBConfg backend"; - } - return "Unknown"; -} - -void* PAL_CALL palAllocate( - const PalAllocator* allocator, - Uint64 size, - Uint64 alignment) -{ - Uint64 align = alignment; - if (align == 0) { - align = PAL_DEFAULT_ALIGNMENT; - } - - if (allocator && allocator->allocate) { - return allocator->allocate(allocator->userData, size, align); - } - return alignedAlloc(size, align); -} - -void PAL_CALL palFree( - const PalAllocator* allocator, - void* ptr) -{ - if (allocator && allocator->free && ptr) { - allocator->free(allocator->userData, ptr); - - } else { - alignedFree(ptr); - } -} - -void PAL_CALL palLog( - const PalLogger* logger, - const char* fmt, - ...) -{ - if (!fmt) { - return; - } - - LogTLSData* data = getLogTlsData(); - va_list argPtr; - va_start(argPtr, fmt); - formatArgs(fmt, argPtr, data->tmp); - va_end(argPtr); - - // check to see if a user supplied a logger - if (logger && logger->callback) { - if (data->isLogging) { - // block recursion - return; - } - - // update the tls to stop recursive calls - memcpy(data->buffer, data->tmp, PAL_LOG_MSG_SIZE); - data->isLogging = true; - updateLogTlsData(data); - logger->callback(logger->userData, data->buffer); - - } else { - // add newline character to the string - format(data->buffer, "%s\n", data->tmp); - writeToConsole(data); - } - - data->isLogging = false; - updateLogTlsData(data); -} - -Uint64 PAL_CALL palGetPerformanceCounter() -{ -#ifdef _WIN32 - LARGE_INTEGER counter; - QueryPerformanceCounter(&counter); - return (Uint64)counter.QuadPart; -#elif defined(__linux__) - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (Uint64)ts.tv_sec * 1000000000LL + (Uint64)ts.tv_nsec; -#endif // _WIN32 -} - -Uint64 PAL_CALL palGetPerformanceFrequency() -{ -#ifdef _WIN32 - LARGE_INTEGER frequency; - QueryPerformanceFrequency(&frequency); - return (Uint64)frequency.QuadPart; -#elif defined(__linux__) - return 1000000000LL; -#endif // _WIN32 -} \ No newline at end of file diff --git a/src/pal_event.c b/src/pal_event.c deleted file mode 100644 index b608dff5..00000000 --- a/src/pal_event.c +++ /dev/null @@ -1,212 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#include "pal/pal_event.h" -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define PAL_MAX_EVENTS 512 - -typedef struct { - Uint8 head; - Uint8 tail; - PalEvent data[PAL_MAX_EVENTS]; -} QueueData; - -struct PalEventDriver { - bool freeQueue; - PalEventQueue* queue; - const PalAllocator* allocator; - PalEventCallback callback; - void* userData; - PalDispatchMode modes[PAL_MAX_EVENTS]; -}; - -// ================================================== -// Internal API -// ================================================== - -static void PAL_CALL defaultPush( - void* queue, - PalEvent* event) -{ - PalEventQueue* eventQueue = queue; - QueueData* data = eventQueue->userData; - data->data[data->tail++ % PAL_MAX_EVENTS] = *event; -} - -static bool PAL_CALL defaultPoll( - void* queue, - PalEvent* outEvent) -{ - PalEventQueue* eventQueue = queue; - QueueData* data = eventQueue->userData; - if (data->head == data->tail) { - return false; - } - - *outEvent = data->data[data->head++ % PAL_MAX_EVENTS]; - return true; -} - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palCreateEventDriver( - const PalEventDriverCreateInfo* info, - PalEventDriver** outEventDriver) -{ - if (!info || !outEventDriver) { - return PAL_RESULT_NULL_POINTER; - } - - if (info->allocator) { - if (!info->allocator->allocate && !info->allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - PalEventDriver* driver = nullptr; - driver = palAllocate(info->allocator, sizeof(PalEventDriver), 0); - if (!driver) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - memset(driver, 0, sizeof(PalEventDriver)); - if (info->allocator) { - driver->allocator = info->allocator; - } - - if (info->queue) { - // user supplied an event queue - driver->queue = info->queue; - driver->freeQueue = false; - - } else { - // we create a default event queue - PalEventQueue* queue = nullptr; - queue = palAllocate(info->allocator, sizeof(PalEventQueue), 0); - if (!queue) { - palFree(info->allocator, driver); - return PAL_RESULT_OUT_OF_MEMORY; - } - - // we create a default event queue data - QueueData* queueData = nullptr; - queueData = palAllocate(info->allocator, sizeof(QueueData), 0); - - if (!queueData) { - palFree(info->allocator, queue); - palFree(info->allocator, driver); - return PAL_RESULT_OUT_OF_MEMORY; - } - - memset(queueData, 0, sizeof(QueueData)); - queue->userData = queueData; - queue->poll = defaultPoll; - queue->push = defaultPush; - - driver->queue = queue; - driver->freeQueue = true; - } - - driver->callback = info->callback; - driver->userData = info->userData; - *outEventDriver = driver; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyEventDriver(PalEventDriver* eventDriver) -{ - if (!eventDriver) { - return; - } - - const PalAllocator* allocator = eventDriver->allocator; - if (eventDriver->freeQueue) { - palFree(allocator, eventDriver->queue->userData); - palFree(allocator, eventDriver->queue); - } - palFree(allocator, eventDriver); -} - -void PAL_CALL palSetEventDispatchMode( - PalEventDriver* eventDriver, - PalEventType type, - PalDispatchMode mode) -{ - if (eventDriver) { - eventDriver->modes[type] = mode; - } -} - -PalDispatchMode PAL_CALL palGetEventDispatchMode( - PalEventDriver* eventDriver, - PalEventType type) -{ - if (!eventDriver) { - return PAL_DISPATCH_NONE; - } - return eventDriver->modes[type]; -} - -void PAL_CALL palPushEvent( - PalEventDriver* eventDriver, - PalEvent* event) -{ - if (!eventDriver || !event) { - return; - } - - // get the event mode - PalDispatchMode mode = eventDriver->modes[event->type]; - if (mode == PAL_DISPATCH_CALLBACK) { - if (eventDriver->callback) { - eventDriver->callback(eventDriver->userData, event); - } - return; // we have dispatched the event - } - - if (mode == PAL_DISPATCH_POLL) { - eventDriver->queue->push(eventDriver->queue, event); - } -} - -bool PAL_CALL palPollEvent( - PalEventDriver* eventDriver, - PalEvent* outEvent) -{ - if (!eventDriver || !outEvent) { - return false; - } - - return eventDriver->queue->poll(eventDriver->queue, outEvent); -} \ No newline at end of file diff --git a/src/pal_platform.h b/src/pal_platform.h new file mode 100644 index 00000000..d512ce23 --- /dev/null +++ b/src/pal_platform.h @@ -0,0 +1,28 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_PLATFORM_H +#define _PAL_PLATFORM_H + +// clang-format off + +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \ + || defined(__NetBSD) || defined(__OpenBSD) +#define _PAL_HAS_POSIX 1 +#else +#define _PAL_HAS_POSIX 0 +#endif // _PAL_HAS_POSIX + +#if defined(__linux__) || defined(__ANDROID__) +#define _PAL_HAS_EGL 1 +#else +#define _PAL_HAS_EGL 0 +#endif // _PAL_HAS_EGL + +// clang-format on + +#endif // _PAL_PLATFORM_H \ No newline at end of file diff --git a/src/system/linux/pal_cpu_linux.c b/src/system/linux/pal_cpu_linux.c new file mode 100644 index 00000000..93b7c27f --- /dev/null +++ b/src/system/linux/pal_cpu_linux.c @@ -0,0 +1,145 @@ +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef __linux__ +#define _POSIX_C_SOURCE 200112L +#include "pal2/pal_system.h" +#include +#include +#include + +static uint32_t parseCache(const char* path) +{ + long cacheSize = 0; + FILE* file = fopen(path, "r"); + if (!file) { + return 0; + } + char cache[16]; + if (fgets(cache, sizeof(cache), file)) { + // get and convert to KB + char unit; + if (sscanf(cache, "%ld%c", &cacheSize, &unit) == 2) { + // check if size is in MB or KB + if (unit == 'M' || unit == 'm') { + cacheSize *= 1024; + } + } + } + fclose(file); + return cacheSize; +} + +void PAL_CALL palGetCPUInfo( + const PalAllocator* allocator, + PalCPUInfo* info) +{ + FILE* file = fopen("/proc/cpuinfo", "r"); + if (!file) { + return; + } + + char line[1024]; + // parse cpu information + while (fgets(line, sizeof(line), file)) { + if (strncmp(line, "vendor_id", 9) == 0) { + sscanf(line, "vendor_id : %15[^\n]", info->vendor); + + } else if (strncmp(line, "model name", 10) == 0) { + sscanf(line, "model name : %63[^\n]", info->model); + + } else if (strncmp(line, "cpu cores", 9) == 0) { + sscanf(line, "cpu cores : %d", &info->numCores); + + } else if (strncmp(line, "flags", 5) == 0) { + // extensions + char* flags = strchr(line, ':'); + if (flags) { + flags++; // skip colon + char* token = strtok(flags, " \t\n"); + while (token) { + if (strcmp(token, "sse") == 0) { + info->features |= PAL_CPU_FEATURE_SSE; + + } else if (strcmp(token, "sse2") == 0) { + info->features |= PAL_CPU_FEATURE_SSE2; + + } else if (strcmp(token, "sse3") == 0) { + info->features |= PAL_CPU_FEATURE_SSE3; + + } else if (strcmp(token, "ssse3") == 0) { + info->features |= PAL_CPU_FEATURE_SSSE3; + + } else if (strcmp(token, "sse4_1") == 0) { + info->features |= PAL_CPU_FEATURE_SSE41; + + } else if (strcmp(token, "sse4_2") == 0) { + info->features |= PAL_CPU_FEATURE_SSE42; + + } else if (strcmp(token, "avx") == 0) { + info->features |= PAL_CPU_FEATURE_AVX; + + } else if (strcmp(token, "avx2") == 0) { + info->features |= PAL_CPU_FEATURE_AVX2; + + } else if (strcmp(token, "avx512f") == 0) { + info->features |= PAL_CPU_FEATURE_AVX512F; + + } else if (strcmp(token, "fma") == 0) { + info->features |= PAL_CPU_FEATURE_FMA3; + + } else if (strcmp(token, "bmi1") == 0) { + info->features |= PAL_CPU_FEATURE_BMI1; + + } else if (strcmp(token, "bmi2") == 0) { + info->features |= PAL_CPU_FEATURE_BMI2; + } + + token = strtok(nullptr, " \t\n"); + } + } + break; // all CPUs are the same + } + } + fclose(file); + + uint32_t l1 = parseCache("/sys/devices/system/cpu/cpu0/cache/index0/size"); + uint32_t l2 = parseCache("/sys/devices/system/cpu/cpu0/cache/index2/size"); + uint32_t l3 = parseCache("/sys/devices/system/cpu/cpu0/cache/index3/size"); + + info->numLogicalProcessors = (uint32_t)sysconf(_SC_NPROCESSORS_ONLN); + info->cacheL1 = l1; + info->cacheL2 = l2; + info->cacheL3 = l3; + + // get architecture + struct utsname arch; + if (uname(&arch) != 0) { + return; + } + + if (strcmp(arch.machine, "x86_64") == 0) { + info->architecture = PAL_CPU_ARCH_X86_64; + } + + if (strcmp(arch.machine, "i386") == 0) { + info->architecture = PAL_CPU_ARCH_X86; + } + + if (strcmp(arch.machine, "i686") == 0) { + info->architecture = PAL_CPU_ARCH_X86; + } + + if (strcmp(arch.machine, "armv71") == 0) { + info->architecture = PAL_CPU_ARCH_ARM; + } + + if (strcmp(arch.machine, "aarch64") == 0) { + info->architecture = PAL_CPU_ARCH_ARM64; + } +} + +#endif // __linux__ \ No newline at end of file diff --git a/src/system/linux/pal_platform_linux.c b/src/system/linux/pal_platform_linux.c new file mode 100644 index 00000000..dff70a2a --- /dev/null +++ b/src/system/linux/pal_platform_linux.c @@ -0,0 +1,67 @@ +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef __linux__ +#define _POSIX_C_SOURCE 200112L +#include "pal2/pal_system.h" +#include +#include +#include +#include + +void PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) +{ + info->type = PAL_PLATFORM_TYPE_LINUX; + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + info->apiType = PAL_PLATFORM_API_TYPE_WAYLAND; + } else { + info->apiType = PAL_PLATFORM_API_TYPE_X11; + } + } else { + // default + info->apiType = PAL_PLATFORM_API_TYPE_X11; + } + + FILE* file = fopen("/etc/os-release", "r"); + if (!file) { + return; + } + + char line[256]; + char name[16]; + char version[16]; + // parse version and name from the release file + while (fgets(line, sizeof(line), file)) { + if (strncmp(line, "NAME=", 5) == 0) { + sscanf(line, "NAME=\"%15[^\"]", name); + + } else if (strncmp(line, "VERSION_ID=", 11) == 0) { + sscanf(line, "VERSION_ID=\"%15[^\"]", version); + } + } + + // combine to get name + snprintf(info->name, PAL_PLATFORM_NAME_SIZE, "%s %s", name, version); + sscanf(version, "%d.%d", &info->version.major, &info->version.minor); + fclose(file); + + // get total memory and disk space for the root drive + struct sysinfo sysInfo; + sysinfo(&sysInfo); + info->totalRAM = (uint32_t)(sysInfo.totalram / (1024 * 1024)); + + struct statvfs stats; + if (statvfs("/", &stats) != 0) { + return; + } + + uint64_t size = (stats.f_blocks * stats.f_frsize) / (1024 * 1024 * 1024); + info->totalMemory = (uint32_t)size; +} + +#endif // __linux__ \ No newline at end of file diff --git a/src/system/pal_system_linux.c b/src/system/pal_system_linux.c deleted file mode 100644 index f775c41d..00000000 --- a/src/system/pal_system_linux.c +++ /dev/null @@ -1,256 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200112L -#include "pal/pal_system.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -// ================================================== -// Internal API -// ================================================== - -static Uint32 parseCache(const char* path) -{ - long cacheSize = 0; - FILE* file = fopen(path, "r"); - if (!file) { - return 0; - } - char cache[16]; - if (fgets(cache, sizeof(cache), file)) { - // get and convert to KB - char unit; - if (sscanf(cache, "%ld%c", &cacheSize, &unit) == 2) { - // check if size is in MB or KB - if (unit == 'M' || unit == 'm') { - cacheSize *= 1024; - } - } - } - fclose(file); - return cacheSize; -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) -{ - if (!info) { - return PAL_RESULT_NULL_POINTER; - } - - info->type = PAL_PLATFORM_LINUX; - const char* session = getenv("XDG_SESSION_TYPE"); - if (session) { - if (strcmp(session, "wayland") == 0) { - info->apiType = PAL_PLATFORM_API_WAYLAND; - } else { - info->apiType = PAL_PLATFORM_API_X11; - } - } else { - // default - info->apiType = PAL_PLATFORM_API_X11; - } - - FILE* file = fopen("/etc/os-release", "r"); - if (!file) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - char line[256]; - char name[15]; - char version[15]; - // parse version and name from the release file - while (fgets(line, sizeof(line), file)) { - if (strncmp(line, "NAME=", 5) == 0) { - sscanf(line, "NAME=\"%15[^\"]", name); - - } else if (strncmp(line, "VERSION_ID=", 11) == 0) { - sscanf(line, "VERSION_ID=\"%15[^\"]", version); - } - } - - // combine to get name - snprintf(info->name, PAL_PLATFORM_NAME_SIZE, "%s %s", name, version); - sscanf(version, "%d.%d", &info->version.major, &info->version.minor); - fclose(file); - - // get total memory and disk space for the root drive - struct sysinfo sysInfo; - sysinfo(&sysInfo); - info->totalRAM = (Uint32)(sysInfo.totalram / (1024 * 1024)); - - struct statvfs stats; - if (statvfs("/", &stats) != 0) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - Uint64 size = (stats.f_blocks * stats.f_frsize) / (1024 * 1024 * 1024); - info->totalMemory = (Uint32)size; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetCPUInfo( - const PalAllocator* allocator, - PalCPUInfo* info) -{ - if (!info) { - return PAL_RESULT_NULL_POINTER; - } - - // check invalid allocator - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - FILE* file = fopen("/proc/cpuinfo", "r"); - if (!file) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - char line[1024]; - // parse cpu information - while (fgets(line, sizeof(line), file)) { - if (strncmp(line, "vendor_id", 9) == 0) { - sscanf(line, "vendor_id : %15[^\n]", info->vendor); - - } else if (strncmp(line, "model name", 10) == 0) { - sscanf(line, "model name : %63[^\n]", info->model); - - } else if (strncmp(line, "cpu cores", 9) == 0) { - sscanf(line, "cpu cores : %d", &info->numCores); - - } else if (strncmp(line, "flags", 5) == 0) { - // extensions - char* flags = strchr(line, ':'); - if (flags) { - flags++; // skip colon - char* token = strtok(flags, " \t\n"); - while (token) { - if (strcmp(token, "sse") == 0) { - info->features |= PAL_CPU_FEATURE_SSE; - - } else if (strcmp(token, "sse2") == 0) { - info->features |= PAL_CPU_FEATURE_SSE2; - - } else if (strcmp(token, "sse3") == 0) { - info->features |= PAL_CPU_FEATURE_SSE3; - - } else if (strcmp(token, "ssse3") == 0) { - info->features |= PAL_CPU_FEATURE_SSSE3; - - } else if (strcmp(token, "sse4_1") == 0) { - info->features |= PAL_CPU_FEATURE_SSE41; - - } else if (strcmp(token, "sse4_2") == 0) { - info->features |= PAL_CPU_FEATURE_SSE42; - - } else if (strcmp(token, "avx") == 0) { - info->features |= PAL_CPU_FEATURE_AVX; - - } else if (strcmp(token, "avx2") == 0) { - info->features |= PAL_CPU_FEATURE_AVX2; - - } else if (strcmp(token, "avx512f") == 0) { - info->features |= PAL_CPU_FEATURE_AVX512F; - - } else if (strcmp(token, "fma") == 0) { - info->features |= PAL_CPU_FEATURE_FMA3; - - } else if (strcmp(token, "bmi1") == 0) { - info->features |= PAL_CPU_FEATURE_BMI1; - - } else if (strcmp(token, "bmi2") == 0) { - info->features |= PAL_CPU_FEATURE_BMI2; - } - - token = strtok(nullptr, " \t\n"); - } - } - break; // all CPUs are the same - } - } - fclose(file); - - Uint32 l1 = parseCache("/sys/devices/system/cpu/cpu0/cache/index0/size"); - Uint32 l2 = parseCache("/sys/devices/system/cpu/cpu0/cache/index2/size"); - Uint32 l3 = parseCache("/sys/devices/system/cpu/cpu0/cache/index3/size"); - - info->numLogicalProcessors = (Uint32)sysconf(_SC_NPROCESSORS_ONLN); - info->cacheL1 = l1; - info->cacheL2 = l2; - info->cacheL3 = l3; - - // get architecture - struct utsname arch; - if (uname(&arch) != 0) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - if (strcmp(arch.machine, "x86_64") == 0) { - info->architecture = PAL_CPU_ARCH_X86_64; - } - - if (strcmp(arch.machine, "i386") == 0) { - info->architecture = PAL_CPU_ARCH_X86; - } - - if (strcmp(arch.machine, "i686") == 0) { - info->architecture = PAL_CPU_ARCH_X86; - } - - if (strcmp(arch.machine, "armv71") == 0) { - info->architecture = PAL_CPU_ARCH_ARM; - } - - if (strcmp(arch.machine, "aarch64") == 0) { - info->architecture = PAL_CPU_ARCH_ARM64; - } - - return PAL_RESULT_SUCCESS; -} \ No newline at end of file diff --git a/src/system/pal_system_win32.c b/src/system/pal_system_win32.c deleted file mode 100644 index bcce0a74..00000000 --- a/src/system/pal_system_win32.c +++ /dev/null @@ -1,375 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#include "pal/pal_system.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// set unicode -#ifndef UNICODE -#define UNICODE -#endif // UNICODE - -#include -#include - -#if defined(_MSC_VER) -#include -#endif // _MSC_VER - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -typedef LONG(WINAPI* RtlGetVersionFn)(PRTL_OSVERSIONINFOW); - -// ================================================== -// Internal API -// ================================================== - -static inline void cpuid( - int regs[4], - int leaf, - int subLeaf) -{ -#if defined(_MSC_VER) - __cpuidex(regs, leaf, subLeaf); -#else - // gcc, clang - __asm__ __volatile__( - "cpuid" - : "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), "=d"(regs[3]) - : "a"(leaf), "b"(subLeaf)); -#endif // _MSC_VER -} - -static inline bool getVersionWin32(PalVersion* version) -{ - OSVERSIONINFOEXW ver = {0}; - ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); - - HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll"); - - // clang-format off - RtlGetVersionFn getVer = (RtlGetVersionFn)GetProcAddress( - ntdll, - "RtlGetVersion"); - // clang-format on - - if (!getVer) { - return false; - } - - if (getVer((PRTL_OSVERSIONINFOW)&ver)) { - return false; - } - - version->major = ver.dwMajorVersion; - version->minor = ver.dwMinorVersion; - version->build = ver.dwBuildNumber; - return true; -} - -static inline bool isVersionWin32( - PalVersion* osVersion, - Uint16 major, - Uint16 minor, - Uint16 build) -{ - if (osVersion->major > major) { - return true; - } - - if (osVersion->major < major) { - return false; - } - - if (osVersion->minor > minor) { - return true; - } - - if (osVersion->minor < minor) { - return false; - } - - return osVersion->build >= build; -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) -{ - if (!info) { - return PAL_RESULT_NULL_POINTER; - } - - info->apiType = PAL_PLATFORM_API_WIN32; - info->type = PAL_PLATFORM_WINDOWS; - - // get windows build, version and combine them - if (!getVersionWin32(&info->version)) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - const char* name = nullptr; - const char* build = nullptr; - // check the versions and set the appropriate name - if (isVersionWin32(&info->version, 5, 1, 0)) { - name = "Windows XP"; - } - - if (isVersionWin32(&info->version, 6, 0, 0)) { - name = "Windows Vista"; - } - - if (isVersionWin32(&info->version, 6, 1, 0)) { - name = "Windows 7"; - } - - if (isVersionWin32(&info->version, 6, 2, 0)) { - name = "Windows 8"; - } - - if (isVersionWin32(&info->version, 6, 3, 0)) { - name = "Windows 8.1"; - } - - if (isVersionWin32(&info->version, 10, 0, 0)) { - name = "Windows 10"; - } - - if (isVersionWin32(&info->version, 10, 0, 22000)) { - name = "Windows 11"; - build = ".22000"; - } - - // combine them into a single string - strcpy(info->name, name); - if (build) { - strcat(info->name, build); - } - - // get total disk memory (size) in GB - ULARGE_INTEGER free, total, available; - if (GetDiskFreeSpaceExW(L"C:\\", &available, &total, &free)) { - info->totalMemory = (Uint32)(total.QuadPart / (1024 * 1024 * 1024)); - } - - // get ram (size) in MB - MEMORYSTATUSEX status = {0}; - status.dwLength = sizeof(MEMORYSTATUSEX); - if (GlobalMemoryStatusEx(&status)) { - info->totalRAM = (Uint32)(status.ullTotalPhys / (1024 * 1024)); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetCPUInfo( - const PalAllocator* allocator, - PalCPUInfo* info) -{ - if (!info) { - return PAL_RESULT_NULL_POINTER; - } - - // check invalid allocator - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - memset(info, 0, sizeof(PalCPUInfo)); - - // get cpu vendor - int regs[4] = {0}; - cpuid(regs, 0, 0); - - memcpy(info->vendor + 0, ®s[1], 4); - memcpy(info->vendor + 4, ®s[3], 4); - memcpy(info->vendor + 8, ®s[2], 4); - info->vendor[12] = '\0'; // null terminating character - - // get cpu model name - cpuid(regs, 0x80000002, 0); - memcpy(info->model, regs, 16); - - cpuid(regs, 0x80000003, 0); - memcpy(info->model + 16, regs, 16); - - cpuid(regs, 0x80000004, 0); - memcpy(info->model + 32, regs, 16); - info->model[48] = '\0'; - - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - info->numLogicalProcessors = sysInfo.dwNumberOfProcessors; - - // get architecture - switch (sysInfo.wProcessorArchitecture) { - case PROCESSOR_ARCHITECTURE_INTEL: { - info->architecture = PAL_CPU_ARCH_X86; - break; - } - - case PROCESSOR_ARCHITECTURE_AMD64: { - info->architecture = PAL_CPU_ARCH_X86_64; - break; - } - - case PROCESSOR_ARCHITECTURE_ARM: { - info->architecture = PAL_CPU_ARCH_ARM; - break; - } - - case PROCESSOR_ARCHITECTURE_ARM64: { - info->architecture = PAL_CPU_ARCH_ARM64; - break; - } - - default: { - info->architecture = PAL_CPU_ARCH_UNKNOWN; - } - } - - // get cpu info - DWORD len = 0; - GetLogicalProcessorInformationEx(RelationAll, nullptr, &len); - - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* buffer = nullptr; - buffer = palAllocate(allocator, len, 16); - if (!buffer) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - BOOL ret = GetLogicalProcessorInformationEx(RelationAll, buffer, &len); - - if (!ret) { - palFree(allocator, buffer); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - char* ptr = (char*)buffer; - while (ptr < (char*)buffer + len) { - SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* tmp = (void*)ptr; - if (tmp->Relationship == RelationProcessorCore) { - info->numCores++; - - } else if (tmp->Relationship == RelationCache) { - // cache size - CACHE_RELATIONSHIP cache = tmp->Cache; - if (cache.Level == 1) { - info->cacheL1 = cache.CacheSize / 1024; - } - - if (cache.Level == 2) { - info->cacheL2 = cache.CacheSize / 1024; - } - - if (cache.Level == 3) { - info->cacheL3 = cache.CacheSize / 1024; - } - } - ptr += tmp->Size; - } - - palFree(allocator, buffer); - - // features - PalCpuFeatures features = 0; - cpuid(regs, 1, 0); - int ecx = regs[2]; - int edx = regs[3]; - - if (edx & (1 << 25)) { - features |= PAL_CPU_FEATURE_SSE; - } - - if (edx & (1 << 26)) { - features |= PAL_CPU_FEATURE_SSE2; - } - - if (ecx & (1 << 0)) { - features |= PAL_CPU_FEATURE_SSE3; - } - - if (ecx & (1 << 9)) { - features |= PAL_CPU_FEATURE_SSSE3; - } - - if (ecx & (1 << 19)) { - features |= PAL_CPU_FEATURE_SSE41; - } - - if (ecx & (1 << 20)) { - features |= PAL_CPU_FEATURE_SSE42; - } - - if (ecx & (1 << 28)) { - features |= PAL_CPU_FEATURE_AVX; - } - - if (ecx & (1 << 12)) { - features |= PAL_CPU_FEATURE_FMA3; - } - - // extended features - cpuid(regs, 7, 0); - int ebx = regs[1]; - - if (ebx & (1 << 5)) { - features |= PAL_CPU_FEATURE_AVX2; - } - - if (ebx & (1 << 3)) { - features |= PAL_CPU_FEATURE_BMI1; - } - - if (ebx & (1 << 8)) { - features |= PAL_CPU_FEATURE_BMI2; - } - - if (ebx & (1 << 16)) { - features |= PAL_CPU_FEATURE_AVX512F; - } - - info->features = features; - return PAL_RESULT_SUCCESS; -} \ No newline at end of file diff --git a/src/system/win32/pal_cpu_win32.c b/src/system/win32/pal_cpu_win32.c new file mode 100644 index 00000000..83c62a7a --- /dev/null +++ b/src/system/win32/pal_cpu_win32.c @@ -0,0 +1,204 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_system.h" +#include +#include + +#if defined(_MSC_VER) +#include +#endif // _MSC_VER + +static inline void cpuid( + int regs[4], + int leaf, + int subLeaf) +{ +#if defined(_MSC_VER) + __cpuidex(regs, leaf, subLeaf); +#else + // gcc, clang + __asm__ __volatile__("cpuid" + : "=a"(regs[0]), "=b"(regs[1]), "=c"(regs[2]), "=d"(regs[3]) + : "a"(leaf), "b"(subLeaf)); +#endif // _MSC_VER +} + +void PAL_CALL palGetCPUInfo( + const PalAllocator* allocator, + PalCPUInfo* info) +{ + memset(info, 0, sizeof(PalCPUInfo)); + + // get cpu vendor + int regs[4] = {0}; + cpuid(regs, 0, 0); + + memcpy(info->vendor + 0, ®s[1], 4); + memcpy(info->vendor + 4, ®s[3], 4); + memcpy(info->vendor + 8, ®s[2], 4); + info->vendor[12] = '\0'; // null terminating character + + // get cpu model name + cpuid(regs, 0x80000002, 0); + memcpy(info->model, regs, 16); + + cpuid(regs, 0x80000003, 0); + memcpy(info->model + 16, regs, 16); + + cpuid(regs, 0x80000004, 0); + memcpy(info->model + 32, regs, 16); + info->model[48] = '\0'; + + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + info->numLogicalProcessors = sysInfo.dwNumberOfProcessors; + + // get architecture + switch (sysInfo.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_INTEL: { + info->architecture = PAL_CPU_ARCH_X86; + break; + } + + case PROCESSOR_ARCHITECTURE_AMD64: { + info->architecture = PAL_CPU_ARCH_X86_64; + break; + } + + case PROCESSOR_ARCHITECTURE_ARM: { + info->architecture = PAL_CPU_ARCH_ARM; + break; + } + + case PROCESSOR_ARCHITECTURE_ARM64: { + info->architecture = PAL_CPU_ARCH_ARM64; + break; + } + + default: { + info->architecture = PAL_CPU_ARCH_UNKNOWN; + } + } + + // get cpu info + DWORD len = 0; + GetLogicalProcessorInformationEx(RelationAll, nullptr, &len); + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* buffer = nullptr; + buffer = palAllocate(allocator, len, 16); + if (!buffer) { + return; + } + + BOOL ret = GetLogicalProcessorInformationEx(RelationAll, buffer, &len); + if (!ret) { + palFree(allocator, buffer); + return; + } + + char* ptr = (char*)buffer; + while (ptr < (char*)buffer + len) { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* tmp = (void*)ptr; + if (tmp->Relationship == RelationProcessorCore) { + info->numCores++; + + } else if (tmp->Relationship == RelationCache) { + // cache size + CACHE_RELATIONSHIP cache = tmp->Cache; + if (cache.Level == 1) { + info->cacheL1 = cache.CacheSize / 1024; + } + + if (cache.Level == 2) { + info->cacheL2 = cache.CacheSize / 1024; + } + + if (cache.Level == 3) { + info->cacheL3 = cache.CacheSize / 1024; + } + } + ptr += tmp->Size; + } + + palFree(allocator, buffer); + + // features + PalCpuFeatures features = 0; + cpuid(regs, 1, 0); + int ecx = regs[2]; + int edx = regs[3]; + + if (edx & (1 << 25)) { + features |= PAL_CPU_FEATURE_SSE; + } + + if (edx & (1 << 26)) { + features |= PAL_CPU_FEATURE_SSE2; + } + + if (ecx & (1 << 0)) { + features |= PAL_CPU_FEATURE_SSE3; + } + + if (ecx & (1 << 9)) { + features |= PAL_CPU_FEATURE_SSSE3; + } + + if (ecx & (1 << 19)) { + features |= PAL_CPU_FEATURE_SSE41; + } + + if (ecx & (1 << 20)) { + features |= PAL_CPU_FEATURE_SSE42; + } + + if (ecx & (1 << 28)) { + features |= PAL_CPU_FEATURE_AVX; + } + + if (ecx & (1 << 12)) { + features |= PAL_CPU_FEATURE_FMA3; + } + + // extended features + cpuid(regs, 7, 0); + int ebx = regs[1]; + + if (ebx & (1 << 5)) { + features |= PAL_CPU_FEATURE_AVX2; + } + + if (ebx & (1 << 3)) { + features |= PAL_CPU_FEATURE_BMI1; + } + + if (ebx & (1 << 8)) { + features |= PAL_CPU_FEATURE_BMI2; + } + + if (ebx & (1 << 16)) { + features |= PAL_CPU_FEATURE_AVX512F; + } + + info->features = features; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/system/win32/pal_platform_win32.c b/src/system/win32/pal_platform_win32.c new file mode 100644 index 00000000..5b52e2e9 --- /dev/null +++ b/src/system/win32/pal_platform_win32.c @@ -0,0 +1,135 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_system.h" +#include +#include + +typedef LONG(WINAPI* RtlGetVersionFn)(PRTL_OSVERSIONINFOW); + +static inline PalBool getVersionWin32(PalVersion* version) +{ + OSVERSIONINFOEXW ver = {0}; + ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll"); + RtlGetVersionFn getVer = (RtlGetVersionFn)GetProcAddress(ntdll, "RtlGetVersion"); + if (!getVer) { + return PAL_FALSE; + } + + if (getVer((PRTL_OSVERSIONINFOW)&ver)) { + return PAL_FALSE; + } + + version->major = ver.dwMajorVersion; + version->minor = ver.dwMinorVersion; + version->build = ver.dwBuildNumber; + return PAL_TRUE; +} + +static inline PalBool isVersionWin32( + PalVersion* osVersion, + uint32_t major, + uint32_t minor, + uint32_t build) +{ + if (osVersion->major > major) { + return PAL_TRUE; + } + + if (osVersion->major < major) { + return PAL_FALSE; + } + + if (osVersion->minor > minor) { + return PAL_TRUE; + } + + if (osVersion->minor < minor) { + return PAL_FALSE; + } + + return osVersion->build >= build; +} + +void PAL_CALL palGetPlatformInfo(PalPlatformInfo* info) +{ + info->apiType = PAL_PLATFORM_API_TYPE_WIN32; + info->type = PAL_PLATFORM_TYPE_WINDOWS; + + // get total disk memory (size) in GB + ULARGE_INTEGER free, total, available; + if (GetDiskFreeSpaceExW(L"C:\\", &available, &total, &free)) { + info->totalMemory = (uint32_t)(total.QuadPart / (1024 * 1024 * 1024)); + } + + // get ram (size) in MB + MEMORYSTATUSEX status = {0}; + status.dwLength = sizeof(MEMORYSTATUSEX); + if (GlobalMemoryStatusEx(&status)) { + info->totalRAM = (uint32_t)(status.ullTotalPhys / (1024 * 1024)); + } + + // get windows build, version and combine them + if (!getVersionWin32(&info->version)) { + return; + } + + const char* name = nullptr; + const char* build = nullptr; + // check the versions and set the appropriate name + if (isVersionWin32(&info->version, 5, 1, 0)) { + name = "Windows XP"; + } + + if (isVersionWin32(&info->version, 6, 0, 0)) { + name = "Windows Vista"; + } + + if (isVersionWin32(&info->version, 6, 1, 0)) { + name = "Windows 7"; + } + + if (isVersionWin32(&info->version, 6, 2, 0)) { + name = "Windows 8"; + } + + if (isVersionWin32(&info->version, 6, 3, 0)) { + name = "Windows 8.1"; + } + + if (isVersionWin32(&info->version, 10, 0, 0)) { + name = "Windows 10"; + } + + if (isVersionWin32(&info->version, 10, 0, 22000)) { + name = "Windows 11"; + build = ".22000"; + } + + // combine them into a single string + strcpy(info->name, name); + if (build) { + strcat(info->name, build); + } +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/thread/pal_thread_linux.c b/src/thread/pal_thread_linux.c deleted file mode 100644 index 2c5b7860..00000000 --- a/src/thread/pal_thread_linux.c +++ /dev/null @@ -1,487 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200112L -#include -#include -#include -#include -#include -#include - -#include "pal/pal_thread.h" - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define TO_PAL_HANDLE(type, val) ((type*)(UintPtr)(val)) -#define FROM_PAL_HANDLE(type, handle) ((type)(UintPtr)(handle)) - -struct PalMutex { - const PalAllocator* allocator; - pthread_mutex_t handle; -}; - -struct PalCondVar { - const PalAllocator* allocator; - pthread_cond_t handle; -}; - -// ================================================== -// Internal API -// ================================================== - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -// ================================================== -// Thread -// ================================================== - -PalResult PAL_CALL palCreateThread( - const PalThreadCreateInfo* info, - PalThread** outThread) -{ - if (!info || !outThread) { - return PAL_RESULT_NULL_POINTER; - } - - if (info->allocator) { - if (!info->allocator->allocate && !info->allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - pthread_t thread; - if (info->stackSize == 0) { - if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - } else { - pthread_attr_t attr; - pthread_attr_init(&attr); - pthread_attr_setstacksize(&attr, info->stackSize); - - if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - pthread_attr_destroy(&attr); - } - - *outThread = TO_PAL_HANDLE(PalThread, thread); - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palJoinThread( - PalThread* thread, - void* retval) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - int ret = 0; - void* value = nullptr; - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - if (retval) { - ret = pthread_join(_thread, &value); - void** out = (void**)retval; - *out = value; - - } else { - ret = pthread_join(_thread, nullptr); - } - - if (ret == 0) { - return PAL_RESULT_SUCCESS; - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } -} - -void PAL_CALL palDetachThread(PalThread* thread) -{ - if (thread) { - pthread_detach(FROM_PAL_HANDLE(pthread_t, thread)); - } -} - -void PAL_CALL palSleep(Uint64 milliseconds) -{ - usleep(milliseconds * 1000); -} - -void PAL_CALL palYield() -{ - sched_yield(); -} - -PalThread* PAL_CALL palGetCurrentThread() -{ - return TO_PAL_HANDLE(PalThread, pthread_self()); -} - -PalThreadFeatures PAL_CALL palGetThreadFeatures() -{ - PalThreadFeatures features = 0; - features |= PAL_THREAD_FEATURE_STACK_SIZE; - features |= PAL_THREAD_FEATURE_PRIORITY; - features |= PAL_THREAD_FEATURE_AFFINITY; - features |= PAL_THREAD_FEATURE_NAME; - return features; -} - -PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread) -{ - if (!thread) { - return 0; - } - - int policy; - struct sched_param param; - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - if (pthread_getschedparam(_thread, &policy, ¶m) != 0) { - return 0; - } - - // set priority - if (policy == SCHED_OTHER || policy == SCHED_RR) { - if (param.sched_priority == 10) { - return PAL_THREAD_PRIORITY_LOW; - - } else if (param.sched_priority == 0) { - return PAL_THREAD_PRIORITY_NORMAL; - } - - } else { - return PAL_THREAD_PRIORITY_HIGH; - } -} - -Uint64 PAL_CALL palGetThreadAffinity(PalThread* thread) -{ - if (!thread) { - return 0; - } - - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - if (pthread_getaffinity_np(_thread, sizeof(cpuset), &cpuset) != 0) { - return 0; - } - - Uint64 mask = 0; - for (int i = 0; i < 64; ++i) { - if (CPU_ISSET(i, &cpuset)) { - mask |= (1ULL << 1); - } - } - return mask; -} - -PalResult PAL_CALL palGetThreadName( - PalThread* thread, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - // see if user provided a buffer and write to it - if (outBuffer && bufferSize > 0) { - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - if (pthread_getname_np(_thread, outBuffer, bufferSize) != 0) { - return PAL_RESULT_INVALID_THREAD; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetThreadPriority( - PalThread* thread, - PalThreadPriority priority) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - switch (priority) { - case PAL_THREAD_PRIORITY_LOW: { - setpriority(PRIO_PROCESS, 0, 10); - break; - } - - case PAL_THREAD_PRIORITY_NORMAL: { - setpriority(PRIO_PROCESS, 0, 0); - break; - } - - case PAL_THREAD_PRIORITY_HIGH: { - struct sched_param param; - param.sched_priority = 10; - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - int ret = pthread_setschedparam(_thread, SCHED_FIFO, ¶m); - if (ret == EPERM) { - return PAL_RESULT_ACCESS_DENIED; - } - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetThreadAffinity( - PalThread* thread, - Uint64 mask) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - for (int i = 0; i < 64; ++i) { - // check for all bits to see which CPUs will be used - if (mask & (1ULL << i)) { - CPU_SET(i, &cpuset); - } - } - - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - int ret = pthread_setaffinity_np(_thread, sizeof(cpuset), &cpuset); - if (ret == 0) { - return PAL_RESULT_SUCCESS; - } else { - return PAL_RESULT_INVALID_THREAD; - } -} - -PalResult PAL_CALL palSetThreadName( - PalThread* thread, - const char* name) -{ - if (!thread || !name) { - return PAL_RESULT_NULL_POINTER; - } - - pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); - int ret = pthread_setname_np(_thread, name); - if (ret == 0) { - return PAL_RESULT_SUCCESS; - } else { - return PAL_RESULT_INVALID_THREAD; - } -} - -// ================================================== -// TLS -// ================================================== - -PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor) -{ - pthread_key_t key; - if (pthread_key_create(&key, destructor) != 0) { - return 0; - } - return (PalTLSId)key; -} - -void PAL_CALL palDestroyTLS(PalTLSId id) -{ - pthread_key_delete((pthread_key_t)id); -} - -void* PAL_CALL palGetTLS(PalTLSId id) -{ - return pthread_getspecific((pthread_key_t)id); -} - -void PAL_CALL palSetTLS( - PalTLSId id, - void* data) -{ - pthread_setspecific((pthread_key_t)id, data); -} - -// ================================================== -// Mutex -// ================================================== - -PalResult PAL_CALL palCreateMutex( - const PalAllocator* allocator, - PalMutex** outMutex) -{ - if (!outMutex) { - return PAL_RESULT_NULL_POINTER; - } - - if (allocator) { - if (!allocator->allocate && !allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - PalMutex* mutex = palAllocate(allocator, sizeof(PalMutex), 0); - if (!mutex) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - pthread_mutex_init(&mutex->handle, nullptr); - mutex->allocator = allocator; - *outMutex = mutex; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyMutex(PalMutex* mutex) -{ - if (mutex) { - pthread_mutex_destroy(&mutex->handle); - palFree(mutex->allocator, mutex); - } -} - -void PAL_CALL palLockMutex(PalMutex* mutex) -{ - if (mutex) { - pthread_mutex_lock(&mutex->handle); - } -} - -void PAL_CALL palUnlockMutex(PalMutex* mutex) -{ - if (mutex) { - pthread_mutex_unlock(&mutex->handle); - } -} - -// ================================================== -// Condition Variable -// ================================================== - -PalResult PAL_CALL palCreateCondVar( - const PalAllocator* allocator, - PalCondVar** outCondVar) -{ - if (!outCondVar) { - return PAL_RESULT_NULL_POINTER; - } - - if (allocator) { - if (!allocator->allocate && !allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - PalCondVar* condVar = palAllocate(allocator, sizeof(PalCondVar), 0); - if (!condVar) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - pthread_cond_init(&condVar->handle, nullptr); - condVar->allocator = allocator; - *outCondVar = condVar; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyCondVar(PalCondVar* condVar) -{ - if (condVar) { - pthread_cond_destroy(&condVar->handle); - palFree(condVar->allocator, condVar); - } -} - -PalResult PAL_CALL palWaitCondVar( - PalCondVar* condVar, - PalMutex* mutex) -{ - if (!condVar || !mutex) { - return PAL_RESULT_NULL_POINTER; - } - - int ret = pthread_cond_wait(&condVar->handle, &mutex->handle); - if (ret == 0) { - return PAL_RESULT_SUCCESS; - } else if (ret == ETIMEDOUT) { - return PAL_RESULT_TIMEOUT; - } else { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } -} - -PalResult PAL_CALL palWaitCondVarTimeout( - PalCondVar* condVar, - PalMutex* mutex, - Uint64 milliseconds) -{ - if (!condVar || !mutex) { - return PAL_RESULT_NULL_POINTER; - } - - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += milliseconds / 1000; - ts.tv_nsec += (milliseconds % 1000) * 1000000; - - if (ts.tv_nsec >= 1000000000) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000; - } - - if (pthread_cond_timedwait(&condVar->handle, &mutex->handle, &ts) != 0) { - return PAL_RESULT_TIMEOUT; - } - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palSignalCondVar(PalCondVar* condVar) -{ - if (condVar) { - pthread_cond_signal(&condVar->handle); - } -} - -void PAL_CALL palBroadcastCondVar(PalCondVar* condVar) -{ - if (condVar) { - pthread_cond_broadcast(&condVar->handle); - } -} \ No newline at end of file diff --git a/src/thread/pal_thread_win32.c b/src/thread/pal_thread_win32.c deleted file mode 100644 index 4337c639..00000000 --- a/src/thread/pal_thread_win32.c +++ /dev/null @@ -1,594 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#include "pal/pal_thread.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// set unicode -#ifndef UNICODE -#define UNICODE -#endif // UNICODE - -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -typedef HRESULT(WINAPI* SetThreadDescriptionFn)( - HANDLE, - PCWSTR); - -typedef HRESULT(WINAPI* GetThreadDescriptionFn)( - HANDLE, - PWSTR*); - -typedef struct { - const PalAllocator* allocator; - PalThreadFn func; - void* arg; -} ThreadData; - -struct PalMutex { - const PalAllocator* allocator; - CRITICAL_SECTION sc; -}; - -struct PalCondVar { - const PalAllocator* allocator; - CONDITION_VARIABLE cv; -}; - -// ================================================== -// Internal API -// ================================================== - -static DWORD WINAPI threadEntryToWin32(LPVOID arg) -{ - ThreadData* data = arg; - void* ret = data->func(data->arg); - palFree(data->allocator, data); - return (DWORD)(uintptr_t)ret; -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// Public API -// ================================================== - -// ================================================== -// Thread -// ================================================== - -PalResult PAL_CALL palCreateThread( - const PalThreadCreateInfo* info, - PalThread** outThread) -{ - if (!info || !outThread) { - return PAL_RESULT_NULL_POINTER; - } - - if (info->allocator) { - if (!info->allocator->allocate && !info->allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - // create thread - ThreadData* data = palAllocate(info->allocator, sizeof(ThreadData), 0); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - data->arg = info->arg; - data->func = info->entry; - data->allocator = info->allocator; - - HANDLE thread = CreateThread( - nullptr, - info->stackSize, - threadEntryToWin32, - data, - 0, - nullptr); - - if (!thread) { - // error - DWORD error = GetLastError(); - if (error == ERROR_NOT_ENOUGH_MEMORY) { - return PAL_RESULT_OUT_OF_MEMORY; - - } else if (error == ERROR_INVALID_PARAMETER) { - return PAL_RESULT_OUT_OF_MEMORY; - - } else if (error == ERROR_ACCESS_DENIED) { - return PAL_RESULT_ACCESS_DENIED; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - *outThread = (PalThread*)thread; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palJoinThread( - PalThread* thread, - void* retval) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - void* value = nullptr; - DWORD wait = WaitForSingleObject((HANDLE)thread, INFINITE); - if (wait == WAIT_OBJECT_0 && retval) { - uintptr_t ret; - GetExitCodeThread((HANDLE)thread, (LPDWORD)&ret); - void** out = (void**)retval; - *out = value; - - // thread is done destroy the HANDLE - CloseHandle((HANDLE)thread); - - } else if (wait == WAIT_FAILED) { - return PAL_RESULT_INVALID_THREAD; - } - - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDetachThread(PalThread* thread) -{ - if (thread) { - CloseHandle((HANDLE)thread); - } -} - -void PAL_CALL palSleep(Uint64 milliseconds) -{ - Sleep((DWORD)milliseconds); -} - -void PAL_CALL palYield() -{ - SwitchToThread(); -} - -PalThread* PAL_CALL palGetCurrentThread() -{ - return (PalThread*)GetCurrentThread(); -} - -PalThreadFeatures PAL_CALL palGetThreadFeatures() -{ - PalThreadFeatures features = 0; - features |= PAL_THREAD_FEATURE_STACK_SIZE; - features |= PAL_THREAD_FEATURE_PRIORITY; - features |= PAL_THREAD_FEATURE_AFFINITY; - - // check support for PAL_THREAD_FEATURE_NAME feature - HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); - if (kernel32) { - // clang-format off - FARPROC setThreadDesc = GetProcAddress( - kernel32, - "SetThreadDescription"); - // clang-format on - - if (setThreadDesc) { - features |= PAL_THREAD_FEATURE_NAME; - } - } - return features; -} - -PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread) -{ - if (!thread) { - return 0; - } - - int priority = GetThreadPriority((HANDLE)thread); - switch (priority) { - case THREAD_PRIORITY_LOWEST: - return PAL_THREAD_PRIORITY_LOW; - break; - - case THREAD_PRIORITY_NORMAL: - return PAL_THREAD_PRIORITY_NORMAL; - break; - - case THREAD_PRIORITY_HIGHEST: - return PAL_THREAD_PRIORITY_HIGH; - break; - } - - return 0; -} - -Uint64 PAL_CALL palGetThreadAffinity(PalThread* thread) -{ - if (!thread) { - return 0; - } - - DWORD_PTR mask = SetThreadAffinityMask((HANDLE)thread, ~0ull); - if (mask == 0) { - return 0; - } - - SetThreadAffinityMask((HANDLE)thread, mask); - return mask; -} - -PalResult PAL_CALL palGetThreadName( - PalThread* thread, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); - GetThreadDescriptionFn getThreadDescription = nullptr; - if (kernel32) { - getThreadDescription = (GetThreadDescriptionFn)GetProcAddress( - kernel32, - "GetThreadDescription"); - } - - if (!getThreadDescription) { - // not supported - return PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED; - } - - wchar_t* buffer = nullptr; - HRESULT hr = getThreadDescription((HANDLE)thread, &buffer); - - if (!SUCCEEDED(hr)) { - return PAL_RESULT_INVALID_THREAD; - } - - int len = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, nullptr, 0, 0, 0); - if (outSize) { - *outSize = len - 1; - } - - // see if user provided a buffer and write to it - if (outBuffer && bufferSize > 0) { - int write = (int)bufferSize - 1; - WideCharToMultiByte(CP_UTF8, 0, buffer, -1, outBuffer, write + 1, 0, 0); - outBuffer[write < len - 1 ? write : len - 1] = '\0'; - LocalFree(buffer); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetThreadPriority( - PalThread* thread, - PalThreadPriority priority) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - int _priority = 0; - switch (priority) { - case PAL_THREAD_PRIORITY_LOW: - _priority = THREAD_PRIORITY_LOWEST; - break; - - case PAL_THREAD_PRIORITY_NORMAL: - _priority = THREAD_PRIORITY_NORMAL; - break; - - case PAL_THREAD_PRIORITY_HIGH: - _priority = THREAD_PRIORITY_HIGHEST; - break; - } - - if (!SetThreadPriority((HANDLE)thread, _priority)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_THREAD; - - } else if (error == ERROR_ACCESS_DENIED) { - return PAL_RESULT_ACCESS_DENIED; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetThreadAffinity( - PalThread* thread, - Uint64 mask) -{ - if (!thread) { - return PAL_RESULT_NULL_POINTER; - } - - if (!SetThreadAffinityMask((HANDLE)thread, mask)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_THREAD; - - } else if (error == ERROR_INVALID_PARAMETER) { - return PAL_RESULT_INVALID_ARGUMENT; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetThreadName( - PalThread* thread, - const char* name) -{ - if (!thread || !name) { - return PAL_RESULT_NULL_POINTER; - } - - HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); - SetThreadDescriptionFn setThreadDescription = nullptr; - if (kernel32) { - setThreadDescription = (SetThreadDescriptionFn)GetProcAddress( - kernel32, - "SetThreadDescription"); - } - - if (!setThreadDescription) { - // not supported - return PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED; - } - - wchar_t buffer[128] = {0}; - MultiByteToWideChar(CP_UTF8, 0, name, -1, buffer, 128); - HRESULT hr = setThreadDescription((HANDLE)thread, buffer); - - if (SUCCEEDED(hr)) { - return PAL_RESULT_SUCCESS; - - } else { - if (hr == E_INVALIDARG) { - return PAL_RESULT_INVALID_THREAD; - - } else if (hr == E_OUTOFMEMORY) { - return PAL_RESULT_OUT_OF_MEMORY; - - } else if (hr == E_ACCESSDENIED) { - return PAL_RESULT_ACCESS_DENIED; - - } else { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } -} - -// ================================================== -// TLS -// ================================================== - -PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor) -{ - DWORD tlsid = FlsAlloc(destructor); - if (tlsid == FLS_OUT_OF_INDEXES) { - return 0; - } - return tlsid; -} - -void PAL_CALL palDestroyTLS(PalTLSId id) -{ - FlsFree((DWORD)id); -} - -void* PAL_CALL palGetTLS(PalTLSId id) -{ - return FlsGetValue((DWORD)id); -} - -void PAL_CALL palSetTLS( - PalTLSId id, - void* data) -{ - FlsSetValue((DWORD)id, data); -} - -// ================================================== -// Mutex -// ================================================== - -PalResult PAL_CALL palCreateMutex( - const PalAllocator* allocator, - PalMutex** outMutex) -{ - if (!outMutex) { - return PAL_RESULT_NULL_POINTER; - } - - if (allocator) { - if (!allocator->allocate && !allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - PalMutex* mutex = palAllocate(allocator, sizeof(PalMutex), 0); - if (!mutex) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - InitializeCriticalSection(&mutex->sc); - mutex->allocator = allocator; - *outMutex = mutex; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyMutex(PalMutex* mutex) -{ - if (mutex) { - DeleteCriticalSection(&mutex->sc); - palFree(mutex->allocator, mutex); - } -} - -void PAL_CALL palLockMutex(PalMutex* mutex) -{ - if (mutex) { - EnterCriticalSection(&mutex->sc); - } -} - -void PAL_CALL palUnlockMutex(PalMutex* mutex) -{ - if (mutex) { - LeaveCriticalSection(&mutex->sc); - } -} - -// ================================================== -// Condition Variable -// ================================================== - -PalResult PAL_CALL palCreateCondVar( - const PalAllocator* allocator, - PalCondVar** outCondVar) -{ - if (!outCondVar) { - return PAL_RESULT_NULL_POINTER; - } - - if (allocator) { - if (!allocator->allocate && !allocator->free) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - } - - PalCondVar* condVar = palAllocate(allocator, sizeof(PalCondVar), 0); - if (!condVar) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - InitializeConditionVariable(&condVar->cv); - condVar->allocator = allocator; - *outCondVar = condVar; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyCondVar(PalCondVar* condVar) -{ - if (condVar) { - palFree(condVar->allocator, condVar); - } -} - -PalResult PAL_CALL palWaitCondVar( - PalCondVar* condVar, - PalMutex* mutex) -{ - if (!condVar || !mutex) { - return PAL_RESULT_NULL_POINTER; - } - - BOOL ret = SleepConditionVariableCS(&condVar->cv, &mutex->sc, INFINITE); - if (!ret) { - DWORD error = GetLastError(); - if (error == ERROR_TIMEOUT) { - return PAL_RESULT_TIMEOUT; - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palWaitCondVarTimeout( - PalCondVar* condVar, - PalMutex* mutex, - Uint64 milliseconds) -{ - if (!condVar || !mutex) { - return PAL_RESULT_NULL_POINTER; - } - - // clang-format off - BOOL ret = SleepConditionVariableCS( - &condVar->cv, - &mutex->sc, - (DWORD)milliseconds); - // clang-format on - - if (!ret) { - DWORD error = GetLastError(); - if (error == ERROR_TIMEOUT) { - return PAL_RESULT_TIMEOUT; - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palSignalCondVar(PalCondVar* condVar) -{ - if (condVar) { - WakeConditionVariable(&condVar->cv); - } -} - -void PAL_CALL palBroadcastCondVar(PalCondVar* condVar) -{ - if (condVar) { - WakeAllConditionVariable(&condVar->cv); - } -} \ No newline at end of file diff --git a/src/thread/posix/pal_condvar_posix.c b/src/thread/posix/pal_condvar_posix.c new file mode 100644 index 00000000..0732d198 --- /dev/null +++ b/src/thread/posix/pal_condvar_posix.c @@ -0,0 +1,92 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#include "pal_thread_posix.h" + +PalResult PAL_CALL palCreateCondVar( + const PalAllocator* allocator, + PalCondVar** outCondVar) +{ + if (!outCondVar) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + PalCondVar* condVar = palAllocate(allocator, sizeof(PalCondVar), 0); + if (!condVar) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + pthread_cond_init(&condVar->handle, nullptr); + condVar->allocator = allocator; + *outCondVar = condVar; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyCondVar(PalCondVar* condVar) +{ + pthread_cond_destroy(&condVar->handle); + palFree(condVar->allocator, condVar); +} + +PalResult PAL_CALL palWaitCondVar( + PalCondVar* condVar, + PalMutex* mutex) +{ + int ret = pthread_cond_wait(&condVar->handle, &mutex->handle); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + + } else if (ret == ETIMEDOUT) { + return palMakeResult(PAL_RESULT_CODE_TIMEOUT, PAL_RESULT_SOURCE_POSIX, errno); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } +} + +PalResult PAL_CALL palWaitCondVarTimeout( + PalCondVar* condVar, + PalMutex* mutex, + uint64_t milliseconds) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + + if (pthread_cond_timedwait(&condVar->handle, &mutex->handle, &ts) != 0) { + return palMakeResult(PAL_RESULT_CODE_TIMEOUT, PAL_RESULT_SOURCE_POSIX, errno); + } + + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palSignalCondVar(PalCondVar* condVar) +{ + pthread_cond_signal(&condVar->handle); +} + +void PAL_CALL palBroadcastCondVar(PalCondVar* condVar) +{ + pthread_cond_broadcast(&condVar->handle); +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/thread/posix/pal_mutex_posix.c b/src/thread/posix/pal_mutex_posix.c new file mode 100644 index 00000000..37001d29 --- /dev/null +++ b/src/thread/posix/pal_mutex_posix.c @@ -0,0 +1,54 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#include "pal_thread_posix.h" + +PalResult PAL_CALL palCreateMutex( + const PalAllocator* allocator, + PalMutex** outMutex) +{ + if (!outMutex) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + PalMutex* mutex = palAllocate(allocator, sizeof(PalMutex), 0); + if (!mutex) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + pthread_mutex_init(&mutex->handle, nullptr); + mutex->allocator = allocator; + *outMutex = mutex; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyMutex(PalMutex* mutex) +{ + pthread_mutex_destroy(&mutex->handle); + palFree(mutex->allocator, mutex); +} + +void PAL_CALL palLockMutex(PalMutex* mutex) +{ + pthread_mutex_lock(&mutex->handle); +} + +void PAL_CALL palUnlockMutex(PalMutex* mutex) +{ + pthread_mutex_unlock(&mutex->handle); +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/thread/posix/pal_thread_posix.c b/src/thread/posix/pal_thread_posix.c new file mode 100644 index 00000000..82a00f57 --- /dev/null +++ b/src/thread/posix/pal_thread_posix.c @@ -0,0 +1,247 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" +#ifdef __linux__ +#define _GNU_SOURCE +#endif // __linux__ + +#if _PAL_HAS_POSIX +#include "pal_thread_posix.h" +#include +#include +#include + +#define TO_PAL_HANDLE(type, val) ((type*)(uintptr_t)(val)) +#define FROM_PAL_HANDLE(type, handle) ((type)(uintptr_t)(handle)) + +PalResult PAL_CALL palCreateThread( + const PalThreadCreateInfo* info, + PalThread** outThread) +{ + if (!info || !outThread) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->allocator) { + if (!info->allocator->allocate && !info->allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + pthread_t thread; + if (info->stackSize == 0) { + if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + } else { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, info->stackSize); + + if (pthread_create(&thread, nullptr, info->entry, info->arg) != 0) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + pthread_attr_destroy(&attr); + } + + *outThread = TO_PAL_HANDLE(PalThread, thread); + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palJoinThread( + PalThread* thread, + void** retval) +{ + int ret = 0; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (retval) { + ret = pthread_join(_thread, retval); + } else { + ret = pthread_join(_thread, nullptr); + } + + if (ret == 0) { + return PAL_RESULT_SUCCESS; + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } +} + +void PAL_CALL palDetachThread(PalThread* thread) +{ + pthread_detach(FROM_PAL_HANDLE(pthread_t, thread)); +} + +void PAL_CALL palSleep(uint64_t milliseconds) +{ + usleep(milliseconds * 1000); +} + +void PAL_CALL palYield() +{ + sched_yield(); +} + +PalThread* PAL_CALL palGetCurrentThread() +{ + return TO_PAL_HANDLE(PalThread, pthread_self()); +} + +PalThreadFeatures PAL_CALL palGetThreadFeatures() +{ + PalThreadFeatures features = 0; + features |= PAL_THREAD_FEATURE_STACK_SIZE; + features |= PAL_THREAD_FEATURE_PRIORITY; + +#ifdef __linux__ + features |= PAL_THREAD_FEATURE_AFFINITY; + features |= PAL_THREAD_FEATURE_NAME; +#endif // __linux__ + + return features; +} + +PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread) +{ + int policy; + struct sched_param param; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (pthread_getschedparam(_thread, &policy, ¶m) != 0) { + return 0; + } + + // set priority + if (policy == SCHED_OTHER || policy == SCHED_RR) { + if (param.sched_priority == 10) { + return PAL_THREAD_PRIORITY_LOW; + + } else if (param.sched_priority == 0) { + return PAL_THREAD_PRIORITY_NORMAL; + } + } + + return PAL_THREAD_PRIORITY_HIGH; +} + +uint64_t PAL_CALL palGetThreadAffinity(PalThread* thread) +{ +#ifdef __linux__ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + if (pthread_getaffinity_np(_thread, sizeof(cpuset), &cpuset) != 0) { + return 0; + } + + uint64_t mask = 0; + for (int i = 0; i < 64; ++i) { + if (CPU_ISSET(i, &cpuset)) { + mask |= (1ULL << 1); + } + } + return mask; +#endif // __linux__ + + return 0; +} + +void PAL_CALL palGetThreadName( + PalThread* thread, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer) +{ +#ifdef __linux__ + // see if user provided a buffer and write to it + if (outBuffer && bufferSize > 0) { + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + pthread_getname_np(_thread, outBuffer, bufferSize); + } +#endif // __linux__ +} + +PalResult PAL_CALL palSetThreadPriority( + PalThread* thread, + PalThreadPriority priority) +{ + switch (priority) { + case PAL_THREAD_PRIORITY_LOW: { + setpriority(PRIO_PROCESS, 0, 10); + break; + } + + case PAL_THREAD_PRIORITY_NORMAL: { + setpriority(PRIO_PROCESS, 0, 0); + break; + } + + case PAL_THREAD_PRIORITY_HIGH: { + struct sched_param param; + param.sched_priority = 10; + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setschedparam(_thread, SCHED_FIFO, ¶m); + if (ret == EPERM) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_OPERATION, + PAL_RESULT_SOURCE_POSIX, + errno); + } + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetThreadAffinity( + PalThread* thread, + uint64_t mask) +{ +#ifdef __linux__ + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + for (int i = 0; i < 64; ++i) { + // check for all bits to see which CPUs will be used + if (mask & (1ULL << i)) { + CPU_SET(i, &cpuset); + } + } + + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setaffinity_np(_thread, sizeof(cpuset), &cpuset); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + + } else { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_POSIX, errno); + } +#endif // __linux__ + + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +PalResult PAL_CALL palSetThreadName( + PalThread* thread, + const char* name) +{ +#ifdef __linux__ + pthread_t _thread = FROM_PAL_HANDLE(pthread_t, thread); + int ret = pthread_setname_np(_thread, name); + if (ret == 0) { + return PAL_RESULT_SUCCESS; + + } else { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_POSIX, errno); + } +#endif // __linux__ + + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/thread/posix/pal_thread_posix.h b/src/thread/posix/pal_thread_posix.h new file mode 100644 index 00000000..ae14211b --- /dev/null +++ b/src/thread/posix/pal_thread_posix.h @@ -0,0 +1,31 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_THREAD_LINUX_H +#define _PAL_THREAD_LINUX_H + +#include "pal_platform.h" +#if _PAL_HAS_POSIX + +#define _POSIX_C_SOURCE 200112L +#include "pal2/pal_thread.h" +#include +#include +#include + +struct PalCondVar { + const PalAllocator* allocator; + pthread_cond_t handle; +}; + +struct PalMutex { + const PalAllocator* allocator; + pthread_mutex_t handle; +}; + +#endif // _PAL_HAS_POSIX +#endif // _PAL_THREAD_LINUX_H \ No newline at end of file diff --git a/src/thread/posix/pal_tls_posix.c b/src/thread/posix/pal_tls_posix.c new file mode 100644 index 00000000..98d516b4 --- /dev/null +++ b/src/thread/posix/pal_tls_posix.c @@ -0,0 +1,40 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_platform.h" + +#if _PAL_HAS_POSIX +#include "pal2/pal_thread.h" +#include + +PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor) +{ + pthread_key_t key; + if (pthread_key_create(&key, destructor) != 0) { + return 0; + } + return (PalTLSId)key; +} + +void PAL_CALL palDestroyTLS(PalTLSId id) +{ + pthread_key_delete((pthread_key_t)id); +} + +void* PAL_CALL palGetTLS(PalTLSId id) +{ + return pthread_getspecific((pthread_key_t)id); +} + +void PAL_CALL palSetTLS( + PalTLSId id, + void* data) +{ + pthread_setspecific((pthread_key_t)id, data); +} + +#endif // _PAL_HAS_POSIX \ No newline at end of file diff --git a/src/thread/win32/pal_condvar_win32.c b/src/thread/win32/pal_condvar_win32.c new file mode 100644 index 00000000..b95575fc --- /dev/null +++ b/src/thread/win32/pal_condvar_win32.c @@ -0,0 +1,86 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_thread_win32.h" + +PalResult PAL_CALL palCreateCondVar( + const PalAllocator* allocator, + PalCondVar** outCondVar) +{ + if (!outCondVar) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + PalCondVar* condVar = palAllocate(allocator, sizeof(PalCondVar), 0); + if (!condVar) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + InitializeConditionVariable(&condVar->cv); + condVar->allocator = allocator; + *outCondVar = condVar; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyCondVar(PalCondVar* condVar) +{ + palFree(condVar->allocator, condVar); +} + +PalResult PAL_CALL palWaitCondVar( + PalCondVar* condVar, + PalMutex* mutex) +{ + BOOL ret = SleepConditionVariableCS(&condVar->cv, &mutex->sc, INFINITE); + if (!ret) { + DWORD error = GetLastError(); + if (error == ERROR_TIMEOUT) { + return palMakeResult(PAL_RESULT_CODE_TIMEOUT, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palWaitCondVarTimeout( + PalCondVar* condVar, + PalMutex* mutex, + uint64_t milliseconds) +{ + BOOL ret = SleepConditionVariableCS(&condVar->cv, &mutex->sc, (DWORD)milliseconds); + if (!ret) { + DWORD error = GetLastError(); + if (error == ERROR_TIMEOUT) { + return palMakeResult(PAL_RESULT_CODE_TIMEOUT, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palSignalCondVar(PalCondVar* condVar) +{ + WakeConditionVariable(&condVar->cv); +} + +void PAL_CALL palBroadcastCondVar(PalCondVar* condVar) +{ + WakeAllConditionVariable(&condVar->cv); +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/thread/win32/pal_mutex_win32.c b/src/thread/win32/pal_mutex_win32.c new file mode 100644 index 00000000..e4c47967 --- /dev/null +++ b/src/thread/win32/pal_mutex_win32.c @@ -0,0 +1,55 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_thread_win32.h" + +PalResult PAL_CALL palCreateMutex( + const PalAllocator* allocator, + PalMutex** outMutex) +{ + if (!outMutex) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (allocator) { + if (!allocator->allocate && !allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + PalMutex* mutex = palAllocate(allocator, sizeof(PalMutex), 0); + if (!mutex) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + InitializeCriticalSection(&mutex->sc); + mutex->allocator = allocator; + *outMutex = mutex; + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDestroyMutex(PalMutex* mutex) +{ + DeleteCriticalSection(&mutex->sc); + palFree(mutex->allocator, mutex); +} + +void PAL_CALL palLockMutex(PalMutex* mutex) +{ + EnterCriticalSection(&mutex->sc); +} + +void PAL_CALL palUnlockMutex(PalMutex* mutex) +{ + LeaveCriticalSection(&mutex->sc); +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/thread/win32/pal_thread_win32.c b/src/thread/win32/pal_thread_win32.c new file mode 100644 index 00000000..20ed4dcc --- /dev/null +++ b/src/thread/win32/pal_thread_win32.c @@ -0,0 +1,282 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_thread_win32.h" + +typedef HRESULT(WINAPI* SetThreadDescriptionFn)( + HANDLE, + PCWSTR); + +typedef HRESULT(WINAPI* GetThreadDescriptionFn)( + HANDLE, + PWSTR*); + +typedef struct { + const PalAllocator* allocator; + PalThreadFn func; + void* arg; +} ThreadData; + +static DWORD WINAPI threadEntryToWin32(LPVOID arg) +{ + ThreadData* data = arg; + void* ret = data->func(data->arg); + palFree(data->allocator, data); + return (DWORD)(uintptr_t)ret; +} + +PalResult PAL_CALL palCreateThread( + const PalThreadCreateInfo* info, + PalThread** outThread) +{ + if (!info || !outThread) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + if (info->allocator) { + if (!info->allocator->allocate && !info->allocator->free) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + } + + // create thread + ThreadData* data = palAllocate(info->allocator, sizeof(ThreadData), 0); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + data->arg = info->arg; + data->func = info->entry; + data->allocator = info->allocator; + + HANDLE thread = CreateThread(nullptr, info->stackSize, threadEntryToWin32, data, 0, nullptr); + if (!thread) { + // error + DWORD error = GetLastError(); + if (error == ERROR_NOT_ENOUGH_MEMORY) { + return palMakeResult(PAL_RESULT_CODE_OUT_OF_MEMORY, PAL_RESULT_SOURCE_WIN32, error); + + } else if (error == ERROR_INVALID_PARAMETER) { + return palMakeResult(PAL_RESULT_CODE_INVALID_ARGUMENT, PAL_RESULT_SOURCE_WIN32, error); + + } else if (error == ERROR_ACCESS_DENIED) { + return palMakeResult(PAL_RESULT_CODE_INVALID_OPERATION, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + + *outThread = (PalThread*)thread; + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palJoinThread( + PalThread* thread, + void** retval) +{ + DWORD wait = WaitForSingleObject((HANDLE)thread, INFINITE); + if (wait == WAIT_OBJECT_0) { + if (retval) { + uintptr_t ret; + GetExitCodeThread((HANDLE)thread, (LPDWORD)&ret); + *retval = (void*)ret; + } + // thread is done destroy the HANDLE + CloseHandle((HANDLE)thread); + + } else if (wait == WAIT_FAILED) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_HANDLE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palDetachThread(PalThread* thread) +{ + CloseHandle((HANDLE)thread); +} + +void PAL_CALL palSleep(uint64_t milliseconds) +{ + Sleep((DWORD)milliseconds); +} + +void PAL_CALL palYield() +{ + SwitchToThread(); +} + +PalThread* PAL_CALL palGetCurrentThread() +{ + return (PalThread*)GetCurrentThread(); +} + +PalThreadFeatures PAL_CALL palGetThreadFeatures() +{ + PalThreadFeatures features = 0; + features |= PAL_THREAD_FEATURE_STACK_SIZE; + features |= PAL_THREAD_FEATURE_PRIORITY; + features |= PAL_THREAD_FEATURE_AFFINITY; + + // check support for PAL_THREAD_FEATURE_NAME feature + HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) { + FARPROC setThreadDesc = GetProcAddress(kernel32, "SetThreadDescription"); + + if (setThreadDesc) { + features |= PAL_THREAD_FEATURE_NAME; + } + } + return features; +} + +PalThreadPriority PAL_CALL palGetThreadPriority(PalThread* thread) +{ + int priority = GetThreadPriority((HANDLE)thread); + switch (priority) { + case THREAD_PRIORITY_LOWEST: + return PAL_THREAD_PRIORITY_LOW; + break; + + case THREAD_PRIORITY_NORMAL: + return PAL_THREAD_PRIORITY_NORMAL; + break; + + case THREAD_PRIORITY_HIGHEST: + return PAL_THREAD_PRIORITY_HIGH; + break; + } + + return 0; +} + +uint64_t PAL_CALL palGetThreadAffinity(PalThread* thread) +{ + DWORD_PTR mask = SetThreadAffinityMask((HANDLE)thread, ~0ull); + if (mask == 0) { + return 0; + } + + SetThreadAffinityMask((HANDLE)thread, mask); + return mask; +} + +void PAL_CALL palGetThreadName( + PalThread* thread, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer) +{ + HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); + GetThreadDescriptionFn getThreadDesc = nullptr; + getThreadDesc = (GetThreadDescriptionFn)GetProcAddress(kernel32, "GetThreadDescription"); + + wchar_t* buffer = nullptr; + getThreadDesc((HANDLE)thread, &buffer); + int len = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, nullptr, 0, 0, 0); + if (outSize) { + *outSize = len - 1; + } + + // see if user provided a buffer and write to it + if (outBuffer && bufferSize > 0) { + int write = (int)bufferSize - 1; + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, outBuffer, write + 1, 0, 0); + outBuffer[write < len - 1 ? write : len - 1] = '\0'; + LocalFree(buffer); + } +} + +PalResult PAL_CALL palSetThreadPriority( + PalThread* thread, + PalThreadPriority priority) +{ + int _priority = 0; + switch (priority) { + case PAL_THREAD_PRIORITY_LOW: + _priority = THREAD_PRIORITY_LOWEST; + break; + + case PAL_THREAD_PRIORITY_NORMAL: + _priority = THREAD_PRIORITY_NORMAL; + break; + + case PAL_THREAD_PRIORITY_HIGH: + _priority = THREAD_PRIORITY_HIGHEST; + break; + } + + if (!SetThreadPriority((HANDLE)thread, _priority)) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_HANDLE) { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_WIN32, error); + + } else if (error == ERROR_ACCESS_DENIED) { + return palMakeResult(PAL_RESULT_CODE_INVALID_OPERATION, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetThreadAffinity( + PalThread* thread, + uint64_t mask) +{ + if (!SetThreadAffinityMask((HANDLE)thread, mask)) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_HANDLE || error == ERROR_INVALID_PARAMETER) { + return palMakeResult(PAL_RESULT_CODE_INVALID_ARGUMENT, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + + return PAL_RESULT_SUCCESS; +} + +PalResult PAL_CALL palSetThreadName( + PalThread* thread, + const char* name) +{ + HINSTANCE kernel32 = GetModuleHandleW(L"kernel32.dll"); + SetThreadDescriptionFn setThreadDesc = nullptr; + setThreadDesc = (SetThreadDescriptionFn)GetProcAddress(kernel32, "SetThreadDescription"); + + wchar_t buffer[128] = {0}; + MultiByteToWideChar(CP_UTF8, 0, name, -1, buffer, 128); + HRESULT hr = setThreadDesc((HANDLE)thread, buffer); + + if (SUCCEEDED(hr)) { + return PAL_RESULT_SUCCESS; + + } else { + if (hr == E_INVALIDARG) { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_WIN32, hr); + + } else if (hr == E_OUTOFMEMORY) { + return palMakeResult(PAL_RESULT_CODE_OUT_OF_MEMORY, PAL_RESULT_SOURCE_WIN32, hr); + + } else if (hr == E_ACCESSDENIED) { + return palMakeResult(PAL_RESULT_CODE_INVALID_OPERATION, PAL_RESULT_SOURCE_WIN32, hr); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, hr); + } + } +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/thread/win32/pal_thread_win32.h b/src/thread/win32/pal_thread_win32.h new file mode 100644 index 00000000..b45499ff --- /dev/null +++ b/src/thread/win32/pal_thread_win32.h @@ -0,0 +1,39 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_THREAD_WIN32_H +#define _PAL_THREAD_WIN32_H +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_thread.h" +#include + +struct PalMutex { + const PalAllocator* allocator; + CRITICAL_SECTION sc; +}; + +struct PalCondVar { + const PalAllocator* allocator; + CONDITION_VARIABLE cv; +}; + +#endif // _WIN32 +#endif // _PAL_THREAD_WIN32_H \ No newline at end of file diff --git a/src/thread/win32/pal_tls_win32.c b/src/thread/win32/pal_tls_win32.c new file mode 100644 index 00000000..d56c68a8 --- /dev/null +++ b/src/thread/win32/pal_tls_win32.c @@ -0,0 +1,51 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_thread.h" +#include + +PalTLSId PAL_CALL palCreateTLS(PaTlsDestructorFn destructor) +{ + DWORD tlsid = FlsAlloc(destructor); + if (tlsid == FLS_OUT_OF_INDEXES) { + return 0; + } + return tlsid; +} + +void PAL_CALL palDestroyTLS(PalTLSId id) +{ + FlsFree((DWORD)id); +} + +void* PAL_CALL palGetTLS(PalTLSId id) +{ + return FlsGetValue((DWORD)id); +} + +void PAL_CALL palSetTLS( + PalTLSId id, + void* data) +{ + FlsSetValue((DWORD)id, data); +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/video/pal_video.c b/src/video/pal_video.c new file mode 100644 index 00000000..486c9cf6 --- /dev/null +++ b/src/video/pal_video.c @@ -0,0 +1,483 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "pal_video_backends.h" +#include "pal_video_egl.h" +#include + +VideoEGL s_VideoEgl = {0}; +static VideoBackend* s_Backend = nullptr; + +static int compareModes( + const void* a, + const void* b) +{ + const PalMonitorMode* mode1 = (const PalMonitorMode*)a; + const PalMonitorMode* mode2 = (const PalMonitorMode*)b; + + // compare width + if (mode1->width > mode2->width) { + return -1; + } + + if (mode1->width < mode2->width) { + return 1; + } + + // compare height + if (mode1->height > mode2->height) { + return -1; + } + + if (mode1->height < mode2->height) { + return 1; + } + + // compare refresh rate + if (mode1->refreshRate > mode2->refreshRate) { + return -1; + } + + if (mode1->refreshRate < mode2->refreshRate) { + return 1; + } + + // compare bpp + if (mode1->bpp > mode2->bpp) { + return -1; + } + + if (mode1->bpp < mode2->bpp) { + return 1; + } + + return 0; +} + +PalResult PAL_CALL palInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance) +{ + // check if x11 or wayland is active + PalBool x11 = PAL_FALSE; + PalBool wayland = PAL_FALSE; + const char* session = getenv("XDG_SESSION_TYPE"); + if (session) { + if (strcmp(session, "wayland") == 0) { + wayland = PAL_TRUE; + } else { + x11 = PAL_TRUE; + } + } + + // initialize the backends +#ifdef _WIN32 + PalResult result = win32InitVideo(allocator, eventDriver, preferredInstance); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + s_Backend = &s_Win32Backend; +#endif // _WIN32 + + if (x11) { +#if PAL_HAS_X11_BACKEND == 1 + PalResult result = xInitVideo(allocator, eventDriver, preferredInstance); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + s_Backend = &s_XBackend; +#endif // PAL_HAS_X11_BACKEND + + } else if (wayland) { +#if PAL_HAS_WAYLAND_BACKEND == 1 + PalResult result = wlInitVideo(allocator, eventDriver, preferredInstance); + if (result != PAL_RESULT_SUCCESS) { + return result; + } + s_Backend = &s_wlBackend; +#endif // PAL_HAS_WAYLAND_BACKEND + } + + // check if we found a backend + if (!s_Backend) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + return PAL_RESULT_SUCCESS; +} + +void PAL_CALL palShutdownVideo() +{ + s_Backend->shutdownVideo(); +} + +void PAL_CALL palUpdateVideo() +{ + s_Backend->updateVideo(); +} + +PalVideoFeatures PAL_CALL palGetVideoFeatures() +{ + return s_Backend->getVideoFeatures(); +} + +PalResult PAL_CALL palEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitors) +{ + if (!count) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->enumerateMonitors(count, outMonitors); +} + +void PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor) +{ + s_Backend->getPrimaryMonitor(outMonitor); +} + +void PAL_CALL palGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + s_Backend->getMonitorInfo(monitor, info); +} + +void PAL_CALL palEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* modes) +{ + s_Backend->enumerateMonitorModes(monitor, count, modes); + if (modes) { + // sort the modes so that they are highest to lowest + qsort(modes, *count, sizeof(PalMonitorMode), compareModes); + } +} + +void PAL_CALL palGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + s_Backend->getCurrentMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return s_Backend->setMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return s_Backend->validateMonitorMode(monitor, mode); +} + +PalResult PAL_CALL palSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + return s_Backend->setMonitorOrientation(monitor, orientation); +} + +PalResult PAL_CALL palCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + if (!info || !outWindow) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->createWindow(info, outWindow); +} + +void PAL_CALL palDestroyWindow(PalWindow* window) +{ + s_Backend->destroyWindow(window); +} + +void PAL_CALL palMinimizeWindow(PalWindow* window) +{ + s_Backend->maximizeWindow(window); +} + +void PAL_CALL palMaximizeWindow(PalWindow* window) +{ + s_Backend->minimizeWindow(window); +} + +void PAL_CALL palRestoreWindow(PalWindow* window) +{ + s_Backend->restoreWindow(window); +} + +void PAL_CALL palShowWindow(PalWindow* window) +{ + s_Backend->showWindow(window); +} + +void PAL_CALL palHideWindow(PalWindow* window) +{ + s_Backend->hideWindow(window); +} + +void PAL_CALL palFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + s_Backend->flashWindow(window, info); +} + +void PAL_CALL palGetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) +{ + s_Backend->getWindowStyle(window, outStyle); +} + +void PAL_CALL palGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + s_Backend->getWindowMonitor(window, outMonitor); +} + +void PAL_CALL palGetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer) +{ + s_Backend->getWindowTitle(window, bufferSize, outSize, outBuffer); +} + +void PAL_CALL palGetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + s_Backend->getWindowPos(window, x, y); +} + +void PAL_CALL palGetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height) +{ + s_Backend->getWindowSize(window, width, height); +} + +void PAL_CALL palGetWindowState( + PalWindow* window, + PalWindowState* outState) +{ + s_Backend->getWindowState(window, outState); +} + +const PalBool* PAL_CALL palGetKeycodeState() +{ + return s_Backend->getKeycodeState(); +} + +const PalBool* PAL_CALL palGetScancodeState() +{ + return s_Backend->getScancodeState(); +} + +const PalBool* PAL_CALL palGetMouseState() +{ + return s_Backend->getMouseState(); +} + +void PAL_CALL palGetMouseDelta( + float* dx, + float* dy) +{ + s_Backend->getMouseDelta(dx, dy); +} + +void PAL_CALL palGetMouseWheelDelta( + float* dx, + float* dy) +{ + s_Backend->getMouseWheelDelta(dx, dy); +} + +PalBool PAL_CALL palIsWindowVisible(PalWindow* window) +{ + return s_Backend->isWindowVisible(window); +} + +PalWindow* PAL_CALL palGetFocusWindow() +{ + return s_Backend->getFocusWindow(); +} + +void PAL_CALL palGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info) +{ + s_Backend->getWindowHandleInfo(window, info); +} + +void PAL_CALL palSetWindowOpacity( + PalWindow* window, + float opacity) +{ + s_Backend->setWindowOpacity(window, opacity); +} + +void PAL_CALL palSetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + s_Backend->setWindowStyle(window, style); +} + +void PAL_CALL palSetWindowTitle( + PalWindow* window, + const char* title) +{ + s_Backend->setWindowTitle(window, title); +} + +void PAL_CALL palSetWindowPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + s_Backend->setWindowPos(window, x, y); +} + +void PAL_CALL palSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height) +{ + s_Backend->setWindowSize(window, width, height); +} + +void PAL_CALL palSetFocusWindow(PalWindow* window) +{ + s_Backend->setFocusWindow(window); +} + +PalResult PAL_CALL palCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + if (!info || !outIcon) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->createIcon(info, outIcon); +} + +void PAL_CALL palDestroyIcon(PalIcon* icon) +{ + s_Backend->destroyIcon(icon); +} + +void PAL_CALL palSetWindowIcon( + PalWindow* window, + PalIcon* icon) +{ + s_Backend->setWindowIcon(window, icon); +} + +PalResult PAL_CALL palCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + if (!info || !outCursor) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->createCursor(info, outCursor); +} + +PalResult PAL_CALL palCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + if (!outCursor) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->createCursorFrom(type, outCursor); +} + +void PAL_CALL palDestroyCursor(PalCursor* cursor) +{ + s_Backend->destroyCursor(cursor); +} + +void PAL_CALL palShowCursor(PalBool show) +{ + s_Backend->showCursor(show); +} + +void PAL_CALL palClipCursor( + PalWindow* window, + PalBool clip) +{ + s_Backend->clipCursor(window, clip); +} + +void PAL_CALL palGetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + s_Backend->getCursorPos(window, x, y); +} + +void PAL_CALL palSetCursorPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + s_Backend->setCursorPos(window, x, y); +} + +void PAL_CALL palSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + s_Backend->setWindowCursor(window, cursor); +} + +PalResult PAL_CALL palAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + if (!windowHandle) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->attachWindow(windowHandle, outWindow); +} + +PalResult PAL_CALL palDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + if (!window) { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + return s_Backend->detachWindow(window, outWindowHandle); +} + +void* PAL_CALL palGetInstance() +{ + return s_Backend->getInstance(); +} diff --git a/src/video/pal_video_backends.h b/src/video/pal_video_backends.h new file mode 100644 index 00000000..e72076bb --- /dev/null +++ b/src/video/pal_video_backends.h @@ -0,0 +1,951 @@ +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_VIDEO_BACKENDS_H +#define _PAL_VIDEO_BACKENDS_H + +#include "pal2/pal_video.h" + +// clang-format off +typedef struct { + void (*shutdownVideo)(); + + void (*updateVideo)(); + + PalVideoFeatures (*getVideoFeatures)(); + + PalResult (*enumerateMonitors)( + uint32_t* count, + PalMonitor** outMonitor); + + void (*getPrimaryMonitor)(PalMonitor** outMonitor); + + void (*getMonitorInfo)( + PalMonitor* monitor, + PalMonitorInfo* info); + + void (*enumerateMonitorModes)( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* mode); + + void (*getCurrentMonitorMode)( + PalMonitor* monitor, + PalMonitorMode* mode); + + PalResult (*setMonitorMode)( + PalMonitor* monitor, + PalMonitorMode* mode); + + PalResult (*validateMonitorMode)( + PalMonitor* monitor, + PalMonitorMode* mode); + + PalResult (*setMonitorOrientation)( + PalMonitor* monitor, + PalOrientation orientation); + + PalResult (*createWindow)( + const PalWindowCreateInfo* info, + PalWindow** outWindow); + + void (*destroyWindow)(PalWindow* window); + + void (*maximizeWindow)(PalWindow* window); + + void (*minimizeWindow)(PalWindow* window); + + void (*restoreWindow)(PalWindow* window); + + void (*showWindow)(PalWindow* window); + + void (*hideWindow)(PalWindow* window); + + void (*flashWindow)( + PalWindow* window, + const PalFlashInfo* info); + + void (*getWindowStyle)( + PalWindow* window, + PalWindowStyle* style); + + void (*getWindowMonitor)( + PalWindow* window, + PalMonitor** outMonitor); + + void (*getWindowTitle)( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer); + + void (*getWindowPos)( + PalWindow* window, + int32_t* x, + int32_t* y); + + void (*getWindowSize)( + PalWindow* window, + uint32_t* width, + uint32_t* height); + + void (*getWindowState)( + PalWindow* window, + PalWindowState* state); + + const PalBool* (*getKeycodeState)(); + + const PalBool* (*getScancodeState)(); + + const PalBool* (*getMouseState)(); + + void (*getMouseDelta)( + float* dx, + float* dy); + + void (*getMouseWheelDelta)( + float* dx, + float* dy); + + PalBool (*isWindowVisible)(PalWindow* window); + + PalWindow* (*getFocusWindow)(); + + void (*getWindowHandleInfo)( + PalWindow* window, + PalWindowHandleInfo* info); + + void (*setWindowOpacity)( + PalWindow* window, + float opacity); + + void (*setWindowStyle)( + PalWindow* window, + PalWindowStyle style); + + void (*setWindowTitle)( + PalWindow* window, + const char* title); + + void (*setWindowPos)( + PalWindow* window, + int32_t x, + int32_t y); + + void (*setWindowSize)( + PalWindow* window, + uint32_t width, + uint32_t height); + + void (*setFocusWindow)(PalWindow* window); + + PalResult (*createIcon)( + const PalIconCreateInfo* info, + PalIcon** outIcon); + + void (*destroyIcon)(PalIcon* icon); + + void (*setWindowIcon)( + PalWindow* window, + PalIcon* icon); + + PalResult (*createCursor)( + const PalCursorCreateInfo* info, + PalCursor** outCursor); + + PalResult (*createCursorFrom)( + PalCursorType type, + PalCursor** outCursor); + + void (*destroyCursor)(PalCursor* cursor); + + void (*showCursor)(PalBool show); + + void (*clipCursor)( + PalWindow* window, + PalBool clip); + + void (*getCursorPos)( + PalWindow* window, + int32_t* x, + int32_t* y); + + void (*setCursorPos)( + PalWindow* window, + int32_t x, + int32_t y); + + void (*setWindowCursor)( + PalWindow* window, + PalCursor* cursor); + + PalResult (*attachWindow)( + void* windowHandle, + PalWindow** outWindow); + + PalResult (*detachWindow)( + PalWindow* window, + void** outWindowHandle); + + void* (*getInstance)(); +} VideoBackend; + +// ================================================== +// WIN32 +// ================================================== + +#ifdef _WIN32 +PalResult win32InitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance); + +void win32ShutdownVideo(); + +void win32UpdateVideo(); + +PalVideoFeatures win32GetVideoFeatures(); + +PalResult win32EnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitor); + +void win32GetPrimaryMonitor(PalMonitor** outMonitor); + +void win32GetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info); + +void win32EnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* mode); + +void win32GetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult win32SetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult win32ValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult win32SetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation); + +PalResult win32CreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow); + +void win32DestroyWindow(PalWindow* window); + +void win32MaximizeWindow(PalWindow* window); + +void win32MinimizeWindow(PalWindow* window); + +void win32RestoreWindow(PalWindow* window); + +void win32ShowWindow(PalWindow* window); + +void win32HideWindow(PalWindow* window); + +void win32FlashWindow( + PalWindow* window, + const PalFlashInfo* info); + +void win32GetWindowStyle( + PalWindow* window, + PalWindowStyle* style); + +void win32GetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor); + +void win32GetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer); + +void win32GetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void win32GetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height); + +void win32GetWindowState( + PalWindow* window, + PalWindowState* state); + +const PalBool* win32GetKeycodeState(); + +const PalBool* win32GetScancodeState(); + +const PalBool* win32GetMouseState(); + +void win32GetMouseDelta( + float* dx, + float* dy); + +void win32GetMouseWheelDelta( + float* dx, + float* dy); + +PalBool win32IsWindowVisible(PalWindow* window); + +PalWindow* win32GetFocusWindow(); + +void win32GetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info); + +void win32SetWindowOpacity( + PalWindow* window, + float opacity); + +void win32SetWindowStyle( + PalWindow* window, + PalWindowStyle style); + +void win32SetWindowTitle( + PalWindow* window, + const char* title); + +void win32SetWindowPos( + PalWindow* window, + int32_t x, + int32_t y); + +void win32SetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height); + +void win32SetFocusWindow(PalWindow* window); + +PalResult win32CreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon); + +void win32DestroyIcon(PalIcon* icon); + +void win32SetWindowIcon( + PalWindow* window, + PalIcon* icon); + +PalResult win32CreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor); + +PalResult win32CreateCursorFrom( + PalCursorType type, + PalCursor** outCursor); + +void win32DestroyCursor(PalCursor* cursor); + +void win32ShowCursor(PalBool show); + +void win32ClipCursor( + PalWindow* window, + PalBool clip); + +void win32GetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void win32SetCursorPos( + PalWindow* window, + int32_t x, + int32_t y); + +void win32SetWindowCursor( + PalWindow* window, + PalCursor* cursor); + +PalResult win32AttachWindow( + void* windowHandle, + PalWindow** outWindow); + +PalResult win32DetachWindow( + PalWindow* window, + void** outWindowHandle); + +void* win32GetInstance(); + +static VideoBackend s_Win32Backend = { + .shutdownVideo = win32ShutdownVideo, + .updateVideo = win32UpdateVideo, + .getVideoFeatures = win32GetVideoFeatures, + .enumerateMonitors = win32EnumerateMonitors, + .getMonitorInfo = win32GetMonitorInfo, + .getPrimaryMonitor = win32GetPrimaryMonitor, + .enumerateMonitorModes = win32EnumerateMonitorModes, + .getCurrentMonitorMode = win32GetCurrentMonitorMode, + .setMonitorMode = win32SetMonitorMode, + .validateMonitorMode = win32ValidateMonitorMode, + .setMonitorOrientation = win32SetMonitorOrientation, + + .createWindow = win32CreateWindow, + .destroyWindow = win32DestroyWindow, + .maximizeWindow = win32MaximizeWindow, + .minimizeWindow = win32MinimizeWindow, + .restoreWindow = win32RestoreWindow, + .showWindow = win32ShowWindow, + .hideWindow = win32HideWindow, + .flashWindow = win32FlashWindow, + .getWindowStyle = win32GetWindowStyle, + .getWindowMonitor = win32GetWindowMonitor, + .getWindowTitle = win32GetWindowTitle, + .getWindowPos = win32GetWindowPos, + .getWindowSize = win32GetWindowSize, + .getWindowState = win32GetWindowState, + .getKeycodeState = win32GetKeycodeState, + .getScancodeState = win32GetScancodeState, + .getMouseState = win32GetMouseState, + .getMouseDelta = win32GetMouseDelta, + .getMouseWheelDelta = win32GetMouseWheelDelta, + .isWindowVisible = win32IsWindowVisible, + .getFocusWindow = win32GetFocusWindow, + .getWindowHandleInfo = win32GetWindowHandleInfo, + .setWindowOpacity = win32SetWindowOpacity, + .setWindowStyle = win32SetWindowStyle, + .setWindowTitle = win32SetWindowTitle, + .setWindowPos = win32SetWindowPos, + .setWindowSize = win32SetWindowSize, + .setFocusWindow = win32SetFocusWindow, + + .createIcon = win32CreateIcon, + .destroyIcon = win32DestroyIcon, + .setWindowIcon = win32SetWindowIcon, + + .createCursor = win32CreateCursor, + .createCursorFrom = win32CreateCursorFrom, + .destroyCursor = win32DestroyCursor, + .showCursor = win32ShowCursor, + .clipCursor = win32ClipCursor, + .getCursorPos = win32GetCursorPos, + .setCursorPos = win32SetCursorPos, + .setWindowCursor = win32SetWindowCursor, + .attachWindow = win32AttachWindow, + .getInstance = win32GetInstance, + .detachWindow = win32DetachWindow}; + +#endif // _WIN32 + +// ================================================== +// X11 +// ================================================== + +#if PAL_HAS_X11_BACKEND == 1 +PalResult xInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance); + +void xShutdownVideo(); + +void xUpdateVideo(); + +PalVideoFeatures xGetVideoFeatures(); + +PalResult xEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitor); + +void xGetPrimaryMonitor(PalMonitor** outMonitor); + +void xGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info); + +void xEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* mode); + +void xGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult xSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult xValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult xSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation); + +PalResult xCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow); + +void xDestroyWindow(PalWindow* window); + +void xMaximizeWindow(PalWindow* window); + +void xMinimizeWindow(PalWindow* window); + +void xRestoreWindow(PalWindow* window); + +void xShowWindow(PalWindow* window); + +void xHideWindow(PalWindow* window); + +void xFlashWindow( + PalWindow* window, + const PalFlashInfo* info); + +void xGetWindowStyle( + PalWindow* window, + PalWindowStyle* style); + +void xGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor); + +void xGetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer); + +void xGetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void xGetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height); + +void xGetWindowState( + PalWindow* window, + PalWindowState* state); + +const PalBool* xGetKeycodeState(); + +const PalBool* xGetScancodeState(); + +const PalBool* xGetMouseState(); + +void xGetMouseDelta( + float* dx, + float* dy); + +void xGetMouseWheelDelta( + float* dx, + float* dy); + +PalBool xIsWindowVisible(PalWindow* window); + +PalWindow* xGetFocusWindow(); + +void xGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info); + +void xSetWindowOpacity( + PalWindow* window, + float opacity); + +void xSetWindowStyle( + PalWindow* window, + PalWindowStyle style); + +void xSetWindowTitle( + PalWindow* window, + const char* title); + +void xSetWindowPos( + PalWindow* window, + int32_t x, + int32_t y); + +void xSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height); + +void xSetFocusWindow(PalWindow* window); + +PalResult xCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon); + +void xDestroyIcon(PalIcon* icon); + +void xSetWindowIcon( + PalWindow* window, + PalIcon* icon); + +PalResult xCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor); + +PalResult xCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor); + +void xDestroyCursor(PalCursor* cursor); + +void xShowCursor(PalBool show); + +void xClipCursor( + PalWindow* window, + PalBool clip); + +void xGetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void xSetCursorPos( + PalWindow* window, + int32_t x, + int32_t y); + +void xSetWindowCursor( + PalWindow* window, + PalCursor* cursor); + +PalResult xAttachWindow( + void* windowHandle, + PalWindow** outWindow); + +PalResult xDetachWindow( + PalWindow* window, + void** outWindowHandle); + +void* xGetInstance(); + +static VideoBackend s_XBackend = { + .shutdownVideo = xShutdownVideo, + .updateVideo = xUpdateVideo, + .getVideoFeatures = xGetVideoFeatures, + .enumerateMonitors = xEnumerateMonitors, + .getMonitorInfo = xGetMonitorInfo, + .getPrimaryMonitor = xGetPrimaryMonitor, + .enumerateMonitorModes = xEnumerateMonitorModes, + .getCurrentMonitorMode = xGetCurrentMonitorMode, + .setMonitorMode = xSetMonitorMode, + .validateMonitorMode = xValidateMonitorMode, + .setMonitorOrientation = xSetMonitorOrientation, + + .createWindow = xCreateWindow, + .destroyWindow = xDestroyWindow, + .maximizeWindow = xMaximizeWindow, + .minimizeWindow = xMinimizeWindow, + .restoreWindow = xRestoreWindow, + .showWindow = xShowWindow, + .hideWindow = xHideWindow, + .flashWindow = xFlashWindow, + .getWindowStyle = nullptr, + .getWindowMonitor = nullptr, + .getWindowTitle = xGetWindowTitle, + .getWindowPos = xGetWindowPos, + .getWindowSize = xGetWindowSize, + .getWindowState = xGetWindowState, + .getKeycodeState = xGetKeycodeState, + .getScancodeState = xGetScancodeState, + .getMouseState = xGetMouseState, + .getMouseDelta = xGetMouseDelta, + .getMouseWheelDelta = xGetMouseWheelDelta, + .isWindowVisible = xIsWindowVisible, + .getFocusWindow = xGetFocusWindow, + .getWindowHandleInfo = xGetWindowHandleInfo, + .setWindowOpacity = xSetWindowOpacity, + .setWindowStyle = nullptr, + .setWindowTitle = xSetWindowTitle, + .setWindowPos = xSetWindowPos, + .setWindowSize = xSetWindowSize, + .setFocusWindow = xSetFocusWindow, + + .createIcon = xCreateIcon, + .destroyIcon = xDestroyIcon, + .setWindowIcon = xSetWindowIcon, + + .createCursor = xCreateCursor, + .createCursorFrom = xCreateCursorFrom, + .destroyCursor = xDestroyCursor, + .showCursor = xShowCursor, + .clipCursor = xClipCursor, + .getCursorPos = xGetCursorPos, + .setCursorPos = xSetCursorPos, + .setWindowCursor = xSetWindowCursor, + .attachWindow = xAttachWindow, + .getInstance = xGetInstance, + .detachWindow = xDetachWindow}; + +#endif // PAL_HAS_X11_BACKEND + +// ================================================== +// Wayland +// ================================================== + +#if PAL_HAS_WAYLAND_BACKEND == 1 +PalResult wlInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance); + +void wlShutdownVideo(); + +void wlUpdateVideo(); + +PalVideoFeatures wlGetVideoFeatures(); + +PalResult wlEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitor); + +void wlGetPrimaryMonitor(PalMonitor** outMonitor); + +void wlGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info); + +void wlEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* mode); + +void wlGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult wlSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult wlValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode); + +PalResult wlSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation); + +PalResult wlCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow); + +void wlDestroyWindow(PalWindow* window); + +void wlMaximizeWindow(PalWindow* window); + +void wlMinimizeWindow(PalWindow* window); + +void wlRestoreWindow(PalWindow* window); + +void wlShowWindow(PalWindow* window); + +void wlHideWindow(PalWindow* window); + +void wlFlashWindow( + PalWindow* window, + const PalFlashInfo* info); + +void wlGetWindowStyle( + PalWindow* window, + PalWindowStyle* style); + +void wlGetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor); + +void wlGetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer); + +void wlGetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void wlGetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height); + +void wlGetWindowState( + PalWindow* window, + PalWindowState* state); + +const PalBool* wlGetKeycodeState(); + +const PalBool* wlGetScancodeState(); + +const PalBool* wlGetMouseState(); + +void wlGetMouseDelta( + float* dx, + float* dy); + +void wlGetMouseWheelDelta( + float* dx, + float* dy); + +PalBool wlIsWindowVisible(PalWindow* window); + +PalWindow* wlGetFocusWindow(); + +void wlGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info); + +void wlSetWindowOpacity( + PalWindow* window, + float opacity); + +void wlSetWindowStyle( + PalWindow* window, + PalWindowStyle style); + +void wlSetWindowTitle( + PalWindow* window, + const char* title); + +void wlSetWindowPos( + PalWindow* window, + int32_t x, + int32_t y); + +void wlSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height); + +void wlSetFocusWindow(PalWindow* window); + +PalResult wlCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon); + +void wlDestroyIcon(PalIcon* icon); + +void wlSetWindowIcon( + PalWindow* window, + PalIcon* icon); + +PalResult wlCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor); + +PalResult wlCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor); + +void wlDestroyCursor(PalCursor* cursor); + +void wlShowCursor(PalBool show); + +void wlClipCursor( + PalWindow* window, + PalBool clip); + +void wlGetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y); + +void wlSetCursorPos( + PalWindow* window, + int32_t x, + int32_t y); + +void wlSetWindowCursor( + PalWindow* window, + PalCursor* cursor); + +PalResult wlAttachWindow( + void* windowHandle, + PalWindow** outWindow); + +PalResult wlDetachWindow( + PalWindow* window, + void** outWindowHandle); + +void* wlGetInstance(); + +static VideoBackend s_wlBackend = { + .shutdownVideo = wlShutdownVideo, + .updateVideo = wlUpdateVideo, + .getVideoFeatures = wlGetVideoFeatures, + .enumerateMonitors = wlEnumerateMonitors, + .getMonitorInfo = wlGetMonitorInfo, + .getPrimaryMonitor = nullptr, + .enumerateMonitorModes = wlEnumerateMonitorModes, + .getCurrentMonitorMode = wlGetCurrentMonitorMode, + .setMonitorMode = wlSetMonitorMode, + .validateMonitorMode = wlValidateMonitorMode, + .setMonitorOrientation = wlSetMonitorOrientation, + + .createWindow = wlCreateWindow, + .destroyWindow = wlDestroyWindow, + .maximizeWindow = wlMaximizeWindow, + .minimizeWindow = wlMinimizeWindow, + .restoreWindow = wlRestoreWindow, + .showWindow = nullptr, + .hideWindow = nullptr, + .flashWindow = nullptr, + .getWindowStyle = nullptr, + .getWindowMonitor = nullptr, + .getWindowTitle = nullptr, + .getWindowPos = nullptr, + .getWindowSize = nullptr, + .getWindowState = nullptr, + .getKeycodeState = wlGetKeycodeState, + .getScancodeState = wlGetScancodeState, + .getMouseState = wlGetMouseState, + .getMouseDelta = wlGetMouseDelta, + .getMouseWheelDelta = wlGetMouseWheelDelta, + .isWindowVisible = wlIsWindowVisible, + .getFocusWindow = nullptr, + .getWindowHandleInfo = wlGetWindowHandleInfo, + .setWindowOpacity = nullptr, + .setWindowStyle = nullptr, + .setWindowTitle = wlSetWindowTitle, + .setWindowPos = nullptr, + .setWindowSize = wlSetWindowSize, + .setFocusWindow = nullptr, + + .createIcon = wlCreateIcon, + .destroyIcon = wlDestroyIcon, + .setWindowIcon = nullptr, + + .createCursor = wlCreateCursor, + .createCursorFrom = wlCreateCursorFrom, + .destroyCursor = wlDestroyCursor, + .showCursor = nullptr, + .clipCursor = nullptr, + .getCursorPos = nullptr, + .setCursorPos = nullptr, + .setWindowCursor = wlSetWindowCursor, + .attachWindow = nullptr, + .getInstance = wlGetInstance, + .detachWindow = nullptr}; + +// clang-format on + +#endif // PAL_HAS_WAYLAND_BACKEND + +#endif // _PAL_VIDEO_BACKENDS_H \ No newline at end of file diff --git a/src/video/pal_video_egl.h b/src/video/pal_video_egl.h new file mode 100644 index 00000000..68d4b4fa --- /dev/null +++ b/src/video/pal_video_egl.h @@ -0,0 +1,81 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_VIDEO_EGL_H +#define _PAL_VIDEO_EGL_H + +#include + +#define EGL_CAST(type, value) ((type)(value)) +#define EGL_OPENGL_API 0x30A2 +#define EGL_OPENGL_BIT 0x0008 +#define EGL_OPENGL_ES_BIT 0x0001 +#define EGL_OPENGL_ES_API 0x30A0 +#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) +#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) +#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) +#define EGL_NATIVE_VISUAL_ID 0x302E + +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void* EGLDisplay; +typedef void* EGLNativeDisplayType; + +typedef int32_t EGLint; +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; + +typedef void* (*eglGetProcAddressFn)(const char*); + +typedef EGLBoolean (*eglInitializeFn)( + EGLDisplay, + EGLint*, + EGLint*); + +typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); + +typedef EGLDisplay (*eglGetDisplayFn)(void*); + +typedef EGLBoolean (*eglChooseConfigFn)( + EGLDisplay, + const EGLint*, + EGLConfig*, + EGLint, + EGLint*); + +typedef EGLBoolean (*eglGetConfigAttribFn)( + EGLDisplay, + EGLConfig, + EGLint, + EGLint*); + +typedef EGLint (*eglGetErrorFn)(void); + +typedef EGLBoolean (*eglBindAPIFn)(EGLenum); + +typedef EGLBoolean (*eglGetConfigsFn)( + EGLDisplay, + EGLConfig*, + EGLint, + EGLint*); + +typedef struct { + void* handle; + eglInitializeFn initialize; + eglTerminateFn terminate; + eglGetDisplayFn getDisplay; + eglChooseConfigFn chooseConfig; + eglGetConfigAttribFn getConfigAttrib; + eglGetErrorFn getError; + eglBindAPIFn bindAPI; + eglGetConfigsFn getConfigs; +} VideoEGL; + +extern VideoEGL s_VideoEgl; + +#endif // _PAL_VIDEO_EGL_H \ No newline at end of file diff --git a/src/video/pal_video_linux.c b/src/video/pal_video_linux.c deleted file mode 100644 index ffe25d33..00000000 --- a/src/video/pal_video_linux.c +++ /dev/null @@ -1,8495 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#define _GNU_SOURCE -#define _POSIX_C_SOURCE 200112L -#include "pal/pal_video.h" - -#include -#include -#include -#include -#include -#include -#include - -// X11 headers -#if PAL_HAS_X11 -#include -#include -#include -#include -#include -#include -#include -#include -#endif // PAL_HAS_X11 - -// Wayland headers -#if PAL_HAS_WAYLAND -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#endif // PAL_HAS_WAYLAND - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define TO_PAL_HANDLE(type, val) ((type*)(UintPtr)(val)) -#define FROM_PAL_HANDLE(type, handle) ((type)(UintPtr)(handle)) -#define MAX_SPAN_MONITORS 4 -#define NULL_BUTTON_SERIAL 0xffffffffU - -#pragma region EGL Typedefs - -typedef void* EGLConfig; -typedef void* EGLSurface; -typedef void* EGLContext; -typedef void* EGLDisplay; -typedef void* EGLNativeDisplayType; - -#define EGL_CAST(type, value) ((type)(value)) -#define EGL_OPENGL_API 0x30A2 -#define EGL_OPENGL_BIT 0x0008 -#define EGL_OPENGL_ES_BIT 0x0001 -#define EGL_OPENGL_ES_API 0x30A0 -#define EGL_NO_CONTEXT EGL_CAST(EGLContext, 0) -#define EGL_NO_DISPLAY EGL_CAST(EGLDisplay, 0) -#define EGL_NO_SURFACE EGL_CAST(EGLSurface, 0) -#define EGL_NATIVE_VISUAL_ID 0x302E - -typedef int32_t EGLint; -typedef unsigned int EGLBoolean; -typedef unsigned int EGLenum; - -typedef void* (*eglGetProcAddressFn)(const char*); - -typedef EGLBoolean (*eglInitializeFn)( - EGLDisplay, - EGLint*, - EGLint*); - -typedef EGLBoolean (*eglTerminateFn)(EGLDisplay); - -typedef EGLDisplay (*eglGetDisplayFn)(void*); - -typedef EGLBoolean (*eglChooseConfigFn)( - EGLDisplay, - const EGLint*, - EGLConfig*, - EGLint, - EGLint*); - -typedef EGLBoolean (*eglGetConfigAttribFn)( - EGLDisplay, - EGLConfig, - EGLint, - EGLint*); - -typedef EGLint (*eglGetErrorFn)(void); - -typedef EGLBoolean (*eglBindAPIFn)(EGLenum); - -typedef EGLBoolean (*eglGetConfigsFn)( - EGLDisplay, - EGLConfig*, - EGLint, - EGLint*); - -#pragma endregion - -typedef struct { - void* monitor; - int dpi; -} SpanMonitor; - -typedef struct { - bool skipConfigure; - bool skipState; - bool used; - bool isAttached; - bool skipIfAttached; - bool focused; - bool pushConfigureEvent; - bool pushStateEvent; - int x; - int y; - Uint32 w; - int dpi; - int monitorCount; - Uint32 h; - PalWindowState state; - PalWindow* window; - - // X11 only - void* ic; - unsigned long colormap; - - // Wayland only - void* xdgSurface; - void* xdgToplevel; - void* buffer; - void* decoration; - void* cursor; - void* eglWindow; - SpanMonitor monitors[MAX_SPAN_MONITORS]; -} WindowData; - -typedef struct { - bool used; - int dpi; - int x; - int y; - Uint32 w; - Uint32 h; - Uint32 refreshRate; - Uint32 wlName; - PalOrientation orientation; - PalMonitor* monitor; - PalMonitorMode mode; // wayland only sends current - char name[32]; -} MonitorData; - -typedef struct { - bool pendingScroll; - Int32 lastX; - Int32 lastY; - Int32 dx; - Int32 dy; - Int32 WheelX; - Int32 WheelY; - float WheelXf; - float WheelYf; - bool state[PAL_MOUSE_BUTTON_MAX]; - double tmpScrollX; - double tmpScrollY; - double accumScrollX; - double accumScrollY; -} Mouse; - -typedef struct { - bool scancodeState[PAL_SCANCODE_MAX]; - bool keycodeState[PAL_KEYCODE_MAX]; - int repeatRate; - int repeatDelay; - int repeatKey; - int repeatScancode; - int scancodes[512]; - int keycodes[256]; - Uint64 timer; - Uint64 frequency; -} Keyboard; - -typedef struct { - void* handle; - eglInitializeFn eglInitialize; - eglTerminateFn eglTerminate; - eglGetDisplayFn eglGetDisplay; - eglChooseConfigFn eglChooseConfig; - eglGetConfigAttribFn eglGetConfigAttrib; - eglGetErrorFn eglGetError; - eglBindAPIFn eglBindAPI; - eglGetConfigsFn eglGetConfigs; -} EGL; - -typedef struct { - // clang-format off - void (*shutdownVideo)(); - void (*updateVideo)(); - PalResult (*setFBConfig)(const int, PalFBConfigBackend); - PalResult (*enumerateMonitors)(Int32*, PalMonitor**); - PalResult (*getPrimaryMonitor)(PalMonitor**); - PalResult (*getMonitorInfo)(PalMonitor*, PalMonitorInfo*); - PalResult (*enumerateMonitorModes)(PalMonitor*, Int32*, PalMonitorMode*); - PalResult (*getCurrentMonitorMode)(PalMonitor*, PalMonitorMode*); - PalResult (*setMonitorMode)(PalMonitor*, PalMonitorMode*); - PalResult (*validateMonitorMode)(PalMonitor*, PalMonitorMode*); - PalResult (*setMonitorOrientation)(PalMonitor*, PalOrientation); - - PalResult (*createWindow)(const PalWindowCreateInfo*, PalWindow**); - void (*destroyWindow)(PalWindow*); - PalResult (*maximizeWindow)(PalWindow*); - PalResult (*minimizeWindow)(PalWindow*); - PalResult (*restoreWindow)(PalWindow*); - PalResult (*showWindow)(PalWindow*); - PalResult (*hideWindow)(PalWindow*); - PalResult (*flashWindow)(PalWindow*, const PalFlashInfo*); - PalResult (*getWindowStyle)(PalWindow*, PalWindowStyle*); - PalResult (*getWindowMonitor)(PalWindow*, PalMonitor**); - PalResult (*getWindowTitle)(PalWindow*, Uint64, Uint64*, char*); - PalResult (*getWindowPos)(PalWindow*, Int32*, Int32*); - PalResult (*getWindowSize)(PalWindow*, Uint32*, Uint32*); - PalResult (*getWindowState)(PalWindow*, PalWindowState*); - bool (*isWindowVisible)(PalWindow*); - PalWindow* (*getFocusWindow)(); - PalWindowHandleInfo (*getWindowHandleInfo)(PalWindow*); - PalWindowHandleInfoEx (*getWindowHandleInfoEx)(PalWindow*); - PalResult (*setWindowOpacity)(PalWindow*, float); - PalResult (*setWindowStyle)(PalWindow*, PalWindowStyle); - PalResult (*setWindowTitle)(PalWindow*, const char*); - PalResult (*setWindowPos)(PalWindow*, Int32, Int32); - PalResult (*setWindowSize)(PalWindow*, Uint32, Uint32); - PalResult (*setFocusWindow)(PalWindow*); - - PalResult (*createIcon)(const PalIconCreateInfo*, PalIcon**); - void (*destroyIcon)(PalIcon*); - PalResult (*setWindowIcon)(PalWindow*, PalIcon*); - - PalResult (*createCursor)(const PalCursorCreateInfo*, PalCursor**); - PalResult (*createCursorFrom)(PalCursorType, PalCursor**); - void (*destroyCursor)(PalCursor*); - void (*showCursor)(bool); - PalResult (*clipCursor)(PalWindow*, bool); - PalResult (*getCursorPos)(PalWindow*, Int32*, Int32*); - PalResult (*setCursorPos)(PalWindow*, Int32, Int32); - PalResult (*setWindowCursor)(PalWindow*, PalCursor*); - - PalResult (*attachWindow)(void*, PalWindow**); - PalResult (*detachWindow)(PalWindow*, void**); - // clang-format off -} Backend; - -typedef struct { - bool initialized; - Int32 maxWindowData; - Int32 maxMonitorData; - Int32 pixelFormat; - PalVideoFeatures features; - PalVideoFeatures64 features64; - const PalAllocator* allocator; - PalEventDriver* eventDriver; - const Backend* backend; - WindowData* windowData; - MonitorData* monitorData; - const char* className; - void* platformInstance; - void* display; -} VideoLinux; - -static VideoLinux s_Video = {0}; -static Mouse s_Mouse = {0}; -static Keyboard s_Keyboard = {0}; -static EGL s_Egl; - -// ================================================== -// X11 Typedefs, enums and structs -// ================================================== - -#pragma region X11 Typedefs -#if PAL_HAS_X11 - -#define X_INTERN(x) s_X11Atoms.x = s_X11.internAtom(s_X11.display, #x, False) -#define RANDR_SCREEN_CHANGE_EVENT 1040 - -// optionally, needed to create visual from FBConfig -#define GLX_FBCONFIG_ID 0x8012 -typedef struct __GLXFBConfigRec* GLXFBConfig; - -typedef GLXFBConfig* (*GLXGetFBConfigsFn)( - Display*, - int, - int*); - -typedef int (*GLXGetFBConfigAttribFn)( - Display*, - GLXFBConfig, - int, - int*); - -typedef XVisualInfo* (*GLXGetVisualFromFBConfigFn)( - Display*, - GLXFBConfig); - -typedef XVisualInfo* (*GLXGetProcAddressFn)(const unsigned char*); - -typedef Display* (*XOpenDisplayFn)(const char*); -typedef int (*XCloseDisplayFn)(Display*); - -typedef int (*XGetWindowAttributesFn)( - Display*, - Window, - XWindowAttributes*); - -typedef int (*XGetWindowPropertyFn)( - Display*, - Window, - Atom, - long, - long, - Bool, - Atom, - Atom*, - int*, - unsigned long*, - unsigned long*, - unsigned char**); - -typedef Atom (*XInternAtomFn)( - Display*, - _Xconst char*, - Bool); - -typedef Window (*XGetSelectionOwnerFn)( - Display*, - Atom); - -typedef Window (*XCreateWindowFn)( - Display*, - Window, - int, - int, - unsigned int, - unsigned int, - unsigned int, - int, - unsigned int, - Visual*, - unsigned long, - XSetWindowAttributes*); - -typedef int (*XChangePropertyFn)( - Display*, - Window, - Atom, - Atom, - int, - int, - _Xconst unsigned char*, - int); - -typedef int (*XFlushFn)(Display*); - -typedef Colormap (*XCreateColormapFn)( - Display*, - Window, - Visual*, - int); - -typedef int (*XFreeColormapFn)( - Display*, - Colormap); - -typedef int (*XDestroyWindowFn)( - Display*, - Window); - -typedef int (*XStoreNameFn)( - Display*, - Window, - _Xconst char*); - -typedef int (*XMapWindowFn)( - Display*, - Window); - -typedef int (*XUnmapWindowFn)( - Display*, - Window); - -typedef int (*XMatchVisualInfoFn)( - Display*, - int, - int, - int, - XVisualInfo*); - -typedef int (*XPendingFn)(Display*); - -typedef int (*XSetWMProtocolsFn)( - Display*, - Window, - Atom*, - int); - -typedef int (*XNextEventFn)( - Display*, - XEvent*); - -typedef int (*XSetWMNormalHintsFn)( - Display*, - Window, - XSizeHints*); - -typedef int (*XGetWMNormalHintsFn)( - Display*, - Window, - XSizeHints*, - long*); - -typedef int (*XSendEventFn)( - Display*, - Window, - Bool, - long, - XEvent*); - -typedef int (*XMoveWindowFn)( - Display*, - Window, - int, - int); - -typedef int (*XResizeWindowFn)( - Display*, - Window, - unsigned int, - unsigned int); - -typedef int (*XIconifyWindowFn)( - Display*, - Window, - int); - -typedef XErrorHandler (*XSetErrorHandlerFn)(XErrorHandler); - -typedef int (*XSyncFn)( - Display*, - Bool); - -typedef int (*XSaveContextFn)( - Display*, - XID, - XContext, - _Xconst char*); - -typedef int (*XFindContextFn)( - Display*, - XID, - XContext, - XPointer*); - -typedef XrmQuark (*XrmUniqueQuarkFn)(void); - -typedef int (*XRRSetCrtcConfigFn)( - Display*, - XRRScreenResources*, - RRCrtc, - Time, - int, - int, - RRMode, - Rotation, - RROutput*, - int); - -typedef XRRScreenResources* (*XRRGetScreenResourcesFn)( - Display*, - Window); - -typedef RROutput (*XRRGetOutputPrimaryFn)( - Display*, - Window); - -typedef XRROutputInfo* (*XRRGetOutputInfoFn)( - Display*, - XRRScreenResources*, - RROutput); - -typedef XRRCrtcInfo* (*XRRGetCrtcInfoFn)( - Display*, - XRRScreenResources*, - RRCrtc); - -typedef void (*XRRFreeScreenResourcesFn)(XRRScreenResources*); - -typedef void (*XRRFreeOutputInfoFn)(XRROutputInfo*); - -typedef void (*XRRFreeCrtcInfoFn)(XRRCrtcInfo*); - -typedef void (*XRRSelectInputFn)( - Display*, - Window, - int); - -typedef int (*XRRQueryExtensionFn)( - Display*, - int*, - int*); - -typedef XClassHint* (*XAllocClassHintFn)(void); - -typedef int (*XSetClassHintFn)( - Display*, - Window, - XClassHint*); - -typedef int (*XFreeFn)(void*); - -typedef Cursor (*XCreateFontCursorFn)( - Display*, - unsigned int); - -typedef int (*XFreePixmapFn)( - Display*, - Pixmap); - -typedef int (*XSetWMHintsFn)( - Display*, - Window, - XWMHints*); - -typedef int (*XGrabPointerFn)( - Display*, - Window, - Bool, - unsigned int, - int, - int, - Window, - Cursor, - Time); - -typedef Cursor (*XCreatePixmapCursorFn)( - Display*, - Pixmap, - Pixmap, - XColor*, - XColor*, - unsigned int, - unsigned int); - -typedef int (*XWarpPointerFn)( - Display*, - Window, - Window, - int, - int, - unsigned int, - unsigned int, - int, - int); - -typedef Status (*XGetWMNameFn)( - Display*, - Window, - XTextProperty*); - -typedef Bool (*XQueryPointerFn)( - Display*, - Window, - Window*, - Window*, - int*, - int*, - int*, - int*, - unsigned int*); - -typedef int (*XUngrabPointerFn)( - Display*, - Time); - -typedef XWMHints* (*XAllocWMHintsFn)(void); - -typedef int (*XMapRaisedFn)( - Display*, - Window); - -typedef int (*XUndefineCursorFn)( - Display*, - Window); - -typedef int (*XDefineCursorFn)( - Display*, - Window, - Cursor); - -typedef int (*XFreeCursorFn)( - Display*, - Cursor); - -typedef XWMHints* (*XGetWMHintsFn)( - Display*, - Window); - -typedef Cursor (*XCreatePixmapCursorFn)( - Display*, - Pixmap, - Pixmap, - XColor*, - XColor*, - unsigned int, - unsigned int); - -typedef int (*XSetInputFocusFn)( - Display*, - Window, - int, - Time); - -typedef int (*XGetInputFocusFn)( - Display*, - Window*, - int*); - -typedef Pixmap (*XCreatePixmapFn)( - Display*, - Drawable, - unsigned int, - unsigned int, - unsigned int); - -typedef XVisualInfo* (*XGetVisualInfoFn)( - Display*, - long, - XVisualInfo*, - int*); - -typedef int (*XSelectInputFn)( - Display*, - Window, - long); - -typedef Cursor (*XcursorImageLoadCursorFn)( - Display*, - const XcursorImage*); - -typedef XcursorImage* (*XcursorImageCreateFn)( - int, - int); - -typedef void (*XcursorImageDestroyFn)(XcursorImage*); - -typedef KeySym (*XLookupKeysymFn)( - XKeyEvent*, - int); - -typedef int (*XkbSetDetectableAutoRepeatFn)( - Display*, - int, - int*); - -typedef char* (*XSetLocaleModifiersFn)(const char*); - -typedef XIM (*XOpenIMFn)( - Display*, - struct _XrmHashBucketRec*, - char*, - char*); - -typedef int (*XCloseIMFn)(XIM); - -typedef XIC (*XCreateICFn)( - XIM, - ...) _X_SENTINEL(0); - -typedef void (*XDestroyICFn)(XIC); - -typedef int (*Xutf8LookupStringFn)( - XIC, - XKeyPressedEvent*, - char*, - int, - KeySym*, - int*); - -typedef struct { - bool unicodeTitle; - - Atom WM_DELETE_WINDOW; - Atom _NET_SUPPORTED; - Atom _NET_WM_STATE; - Atom _NET_WM_STATE_ABOVE; - Atom _NET_WM_STATE_HIDDEN; - Atom _NET_WM_STATE_MAXIMIZED_VERT; - Atom _NET_WM_STATE_MAXIMIZED_HORZ; - Atom _NET_WM_WINDOW_TYPE_UTILITY; - Atom _NET_WM_DESKTOP; - Atom _NET_WM_STATE_DEMANDS_ATTENTIONS; - Atom _NET_WM_WINDOW_OPACITY; - - Atom _NET_WM_NAME; - Atom UTF8_STRING; - Atom _NET_WM_WINDOW_TYPE; - Atom _NET_WM_WINDOW_TYPE_SPLASH; - Atom _NET_WM_PID; - Atom _WM_CLASS; - Atom _NET_ACTIVE_WINDOW; - Atom _NET_WM_ICON; -} X11Atoms; - -typedef struct { - bool error; - bool skipScreenEvent; - int bpp; - int screen; - int depth; - int monitorCount; - int rrEventBase; - void* handle; - void* xrandr; - void* glxHandle; - void* libCursor; - XIM im; - Display* display; - Window root; - XContext dataID; - XVisualInfo* visualInfo; - - XOpenDisplayFn openDisplay; - XCloseDisplayFn closeDisplay; - XGetWindowAttributesFn getWindowAttributes; - XGetWindowPropertyFn getWindowProperty; - XInternAtomFn internAtom; - XGetSelectionOwnerFn getSelectionOwner; - XFreeColormapFn freeColormap; - XStoreNameFn storeName; - XChangePropertyFn changeProperty; - XFlushFn flush; - XCreateColormapFn createColormap; - XMapWindowFn mapWindow; - XUnmapWindowFn unmapWindow; - - XCreateWindowFn createWindow; - XDestroyWindowFn destroyWindow; - XMatchVisualInfoFn matchVisualInfo; - XPendingFn pending; - XSetWMProtocolsFn setWMProtocols; - XNextEventFn nextEvent; - XSetWMNormalHintsFn setWMNormalHints; - XGetWMNormalHintsFn getWMNormalHints; - XSendEventFn sendEvent; - XMoveWindowFn moveWindow; - XResizeWindowFn resizeWindow; - XIconifyWindowFn iconifyWindow; - - XSetErrorHandlerFn setErrorHandler; - XSyncFn sync; - XSaveContextFn saveContext; - XFindContextFn findContext; - XrmUniqueQuarkFn uniqueContext; - - XRRSetCrtcConfigFn setCrtcConfig; - XRRGetScreenResourcesFn getScreenResources; - XRRGetOutputPrimaryFn getOutputPrimary; - XRRGetOutputInfoFn getOutputInfo; - XRRGetCrtcInfoFn getCrtcInfo; - XRRFreeScreenResourcesFn freeScreenResources; - XRRFreeOutputInfoFn freeOutputInfo; - XRRFreeCrtcInfoFn freeCrtcInfo; - XRRSelectInputFn selectRRInput; - XRRQueryExtensionFn queryRRExtension; - - XAllocClassHintFn allocClassHint; - XSetClassHintFn setClassHint; - XFreeFn free; - XGetVisualInfoFn getVisualInfo; - - // opengl - GLXGetFBConfigsFn glxGetFBConfigs; - GLXGetFBConfigAttribFn glxGetFBConfigAttrib; - GLXGetVisualFromFBConfigFn glxGetVisualFromFBConfig; - - XCreateFontCursorFn createFontCursor; - XFreePixmapFn freePixmap; - XSetWMHintsFn setWMHints; - XGrabPointerFn grabPointer; - XCreatePixmapCursorFn createPixmapCursor; - XWarpPointerFn warpPointer; - XGetWMNameFn getWMName; - XQueryPointerFn queryPointer; - XUngrabPointerFn ungrabPointer; - XAllocWMHintsFn allocWMHints; - XMapRaisedFn mapRaised; - XUndefineCursorFn undefineCursor; - XDefineCursorFn defineCursor; - XFreeCursorFn freeCursor; - XGetWMHintsFn getWMHints; - XCreatePixmapFn createPixmap; - XSetInputFocusFn setInputFocus; - XGetInputFocusFn getInputFocus; - XcursorImageLoadCursorFn cursorImageLoadCursor; - XcursorImageCreateFn cursorImageCreate; - XcursorImageDestroyFn cursorImageDestroy; - XLookupKeysymFn lookupKeysym; - XSelectInputFn selectInput; - - XkbSetDetectableAutoRepeatFn setDetectableAutoRepeat; - XSetLocaleModifiersFn setLocaleModifiers; - XOpenIMFn openIM; - XCloseIMFn closeIM; - XCreateICFn createIC; - XDestroyICFn destroyIC; - Xutf8LookupStringFn utf8LookupString; -} X11; - -static X11 s_X11 = {0}; -static X11Atoms s_X11Atoms = {0}; - -#endif // PAL_HAS_X11 -#pragma endregion - -// ================================================== -// Wayland Typedefs, enums and structs -// ================================================== - -#pragma region Wayland Typedefs -#if PAL_HAS_WAYLAND - -typedef struct wl_display* (*wl_display_connect_fn)(const char*); -typedef void (*wl_display_disconnect_fn)(struct wl_display*); -typedef int (*wl_display_roundtrip_fn)(struct wl_display*); -typedef int (*wl_display_dispatch_fn)(struct wl_display*); -typedef void (*wl_proxy_destroy_fn)(struct wl_proxy*); - -typedef int (*wl_proxy_add_listener_fn)( - struct wl_proxy*, - void (**)(void), void*); - -typedef struct wl_proxy* (*wl_proxy_marshal_constructor_v_fn)( - struct wl_proxy*, - uint32_t, - const struct wl_interface*, - uint32_t, ...); - -typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( - struct wl_proxy*, - uint32_t, - const struct wl_interface*, - uint32_t, - uint32_t, ...); - -typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); - -typedef int (*wl_proxy_add_listener_fn)( - struct wl_proxy*, - void (**)(void), void*); - -typedef int (*wl_display_get_error_fn)(struct wl_display*); -typedef int (*wl_display_dispatch_pending_fn)(struct wl_display*); -typedef int (*wl_display_flush_fn)(struct wl_display*); -typedef int (*wl_display_prepare_read_fn)(struct wl_display*); -typedef int (*wl_display_read_events_fn)(struct wl_display*); -typedef int (*wl_display_get_fd_fn)(struct wl_display*); -typedef void (*wl_display_cancel_read_fn)(struct wl_display*); - -// xkb -typedef void (*xkb_keymap_unref_fn)(struct xkb_keymap*); -typedef struct xkb_state* (*xkb_state_new_fn)(struct xkb_keymap*); -typedef void (*xkb_state_unref_fn)(struct xkb_state*); -typedef void (*xkb_context_unref_fn)(struct xkb_context*); -typedef struct xkb_context* (*xkb_context_new_fn)(enum xkb_context_flags); -typedef uint32_t (*xkb_keysym_to_utf32_fn)(xkb_keysym_t); - -typedef xkb_keysym_t (*xkb_state_key_get_one_sym_fn)( - struct xkb_state*, - xkb_keycode_t); - -typedef struct xkb_keymap* (*xkb_keymap_new_from_string_fn)( - struct xkb_context*, - const char*, - enum xkb_keymap_format, - enum xkb_keymap_compile_flags); - -typedef enum xkb_state_component (*xkb_state_update_mask_fn)( - struct xkb_state*, - xkb_mod_mask_t, - xkb_mod_mask_t, - xkb_mod_mask_t, - xkb_layout_index_t, - xkb_layout_index_t, - xkb_layout_index_t); - -typedef int (*xkb_keymap_key_repeats_fn)( - struct xkb_keymap*, - xkb_keycode_t); - -// wayland cursor -typedef struct wl_cursor_theme* (*wl_cursor_theme_load_fn)( - const char*, - int, - struct wl_shm*); - -typedef struct wl_cursor* (*wl_cursor_theme_get_cursor_fn)( - struct wl_cursor_theme*, - const char*); - -typedef struct wl_buffer* (*wl_cursor_image_get_buffer_fn)( - struct wl_cursor_image*); - -// egl_window -struct wl_egl_window; -struct wl_surface; - -typedef struct wl_egl_window* (*wl_egl_window_create_fn)( - struct wl_surface*, - int, - int); - -typedef void (*wl_egl_window_destroy_fn)(struct wl_egl_window*); - -typedef void (*wl_egl_window_resize_fn)( - struct wl_egl_window*, - int, - int, - int, - int); - -typedef struct { - bool checkFeatures; - int monitorCount; - - void* handle; - void* xkbCommon; - void* libCursor; - void* libWaylandEgl; - struct wl_display* display; - struct wl_registry* registry; - struct xdg_wm_base* xdgBase; - struct wl_compositor* compositor; - struct wl_shm* shm; - struct wl_seat* seat; - struct wl_pointer* pointer; - struct wl_keyboard* keyboard; - struct zxdg_decoration_manager_v1* decorationManager; - struct zwp_pointer_constraints* pointerConstraints; - struct wl_surface* pointerSurface; - struct wl_surface* keyboardSurface; - struct wl_cursor_theme* cursorTheme; - - struct xkb_context* inputContext; - struct xkb_keymap* keymap; - struct xkb_state* state; - - const struct wl_interface* outputInterface; - const struct wl_interface* seatInterface; - const struct wl_interface* compositorInterface; - const struct wl_interface* registryInterface; - const struct wl_interface* surfaceInterface; - const struct wl_interface* shmInterface; - const struct wl_interface* bufferInterface; - const struct wl_interface* shmPoolInterface; - const struct wl_interface* regionInterface; - const struct wl_interface* pointerInterface; - const struct wl_interface* keyboardInterface; - - wl_display_connect_fn displayConnect; - wl_display_disconnect_fn displayDisconnect; - wl_display_roundtrip_fn displayRoundtrip; - wl_display_dispatch_fn displayDispatch; - wl_proxy_destroy_fn proxyDestroy; - wl_proxy_add_listener_fn proxyAddListener; - wl_proxy_marshal_constructor_v_fn proxyMarshalCnstructor; - wl_proxy_marshal_flags_fn proxyMarshalFlags; - wl_proxy_get_version_fn proxyGetVersion; - wl_display_get_error_fn getError; - wl_display_dispatch_pending_fn dispatchPending; - wl_display_flush_fn displayFlush; - wl_display_prepare_read_fn prepareRead; - wl_display_read_events_fn readEvents; - wl_display_get_fd_fn displayGetFd; - wl_display_cancel_read_fn cancelRead; - - xkb_keymap_unref_fn xkbKeymapUnref; - xkb_state_new_fn xkbStateNew; - xkb_state_unref_fn xkbStateUnref; - xkb_context_unref_fn xkbContextUnref; - xkb_context_new_fn xkbContextNew; - xkb_state_key_get_one_sym_fn xkbStateKeyGetOneSym; - xkb_keymap_new_from_string_fn xkbKeymapNewFromString; - xkb_keysym_to_utf32_fn xkbKeysymToUtf32; - xkb_state_update_mask_fn xkbStateUpdateMask; - xkb_keymap_key_repeats_fn xkbKeymapKeyRepeats; - - wl_cursor_theme_load_fn cursorThemeLoad; - wl_cursor_theme_get_cursor_fn cursorThemeGetCursor; - wl_cursor_image_get_buffer_fn cursorImageGetBuffer; - - EGLConfig eglFBConfig; - wl_egl_window_create_fn eglWindowCreate; - wl_egl_window_destroy_fn eglWindowDestroy; - wl_egl_window_resize_fn eglWindowResize; -} Wayland; - -typedef struct { - struct wl_buffer* buffer; - struct wl_surface* surface; - int hotspotX; - int hotspotY; -} WaylandCursor; - -static Wayland s_Wl = {0}; - -#endif // PAL_HAS_WAYLAND -#pragma endregion - -#pragma region Wayland-Client-Protocol -#if PAL_HAS_WAYLAND - -static WindowData* findWindowData(PalWindow* window); -static MonitorData* findMonitorData(PalMonitor* monitor); - -static inline Uint64 getTime() -{ - Uint64 now = palGetPerformanceCounter(); - return (now * 1000) / s_Keyboard.frequency; -} - -static inline void* wlRegistryBind( - struct wl_registry *wl_registry, - uint32_t name, - const struct wl_interface *interface, - uint32_t version) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *)wl_registry, - WL_REGISTRY_BIND, - interface, - version, - 0, - name, - interface->name, - version, - NULL); - - return (void *)id; -} - -static inline int wlRegistryAddListener( - struct wl_registry *wl_registry, - const struct wl_registry_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_registry, - (void (**)(void)) listener, data); -} - -static inline struct wl_registry* wlDisplayGetRegistry( - struct wl_display *wl_display) -{ - struct wl_proxy *registry; - registry = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_display, - 1, // WL_DISPLAY_GET_REGISTRY - s_Wl.registryInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_display), - 0, - NULL); - - return (struct wl_registry *)registry; -} - -static inline int wlOutputAddListener( - struct wl_output *wl_output, - const struct wl_output_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_output, - (void (**)(void)) listener, data); -} - -static inline struct wl_surface* wlCompositorCreateSurface( - struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_compositor, - 0, // WL_COMPOSITOR_CREATE_SURFACE, - s_Wl.surfaceInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_compositor), - 0, - NULL); - - return (struct wl_surface*) id; -} - -static inline void wlSurfaceCommit(struct wl_surface *wl_surface) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_surface, - 6, // WL_SURFACE_COMMIT - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_surface), - 0); -} - -static inline void wlSurfaceDestroy(struct wl_surface *wl_surface) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_surface, - 0, // WL_SURFACE_DESTROY - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_surface), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline struct wl_shm_pool* wlShmCreatePool( - struct wl_shm *wl_shm, - int32_t fd, - int32_t size) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_shm, - 0, // WL_SHM_CREATE_POOL - s_Wl.shmPoolInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_shm), - 0, - NULL, - fd, - size); - - return (struct wl_shm_pool *) id; -} - -static inline void wlShmPoolDestroy(struct wl_shm_pool *wl_shm_pool) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_DESTROY, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_shm_pool), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline struct wl_buffer* wlShmPoolCreateBuffer( - struct wl_shm_pool *wl_shm_pool, - int32_t offset, - int32_t width, - int32_t height, - int32_t stride, - uint32_t format) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_shm_pool, - WL_SHM_POOL_CREATE_BUFFER, - s_Wl.bufferInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_shm_pool), - 0, - NULL, - offset, - width, - height, - stride, - format); - - return (struct wl_buffer *) id; -} - -static inline void wlBufferDestroy(struct wl_buffer *wl_buffer) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_buffer, - WL_BUFFER_DESTROY, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_buffer), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline void wlSurfaceAttach( - struct wl_surface *wl_surface, - struct wl_buffer *buffer, - int32_t x, - int32_t y) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_surface, - WL_SURFACE_ATTACH, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_surface), - 0, - buffer, - x, - y); -} - -static inline void wlSurfaceDamageBuffer( - struct wl_surface *wl_surface, - int32_t x, - int32_t y, - int32_t width, - int32_t height) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_surface, - WL_SURFACE_DAMAGE_BUFFER, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_surface), - 0, - x, - y, - width, - height); -} - -static inline int wlSurfaceAddListener( - struct wl_surface *wl_surface, - const struct wl_surface_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_surface, - (void (**)(void)) listener, data); -} - -static inline int wlSeatAddListener( - struct wl_seat *wl_seat, - const struct wl_seat_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_seat, - (void (**)(void)) listener, data); -} - -static inline struct wl_pointer* wlSeatGetPointer(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_seat, - WL_SEAT_GET_POINTER, - s_Wl.pointerInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_seat), - 0, - NULL); - - return (struct wl_pointer *) id; -} - -static inline struct wl_keyboard* wlSeatGetKeyboard(struct wl_seat *wl_seat) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_seat, - WL_SEAT_GET_KEYBOARD, - s_Wl.keyboardInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_seat), - 0, - NULL); - - return (struct wl_keyboard *) id; -} - -static inline int wlPointerAddListener( - struct wl_pointer *wl_pointer, - const struct wl_pointer_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_pointer, - (void (**)(void)) listener, data); -} - -static inline void wlPointerSetCursor( - struct wl_pointer *wl_pointer, - uint32_t serial, - struct wl_surface *surface, - int32_t hotspot_x, - int32_t hotspot_y) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_pointer, - WL_POINTER_SET_CURSOR, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_pointer), - 0, - serial, - surface, - hotspot_x, - hotspot_y); -} - -static inline int wlKeyboardAddListener( - struct wl_keyboard *wl_keyboard, - const struct wl_keyboard_listener *listener, - void *data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy *) wl_keyboard, - (void (**)(void)) listener, data); -} - -static inline struct wl_region* wlCompositorCreateRegion( - struct wl_compositor *wl_compositor) -{ - struct wl_proxy *id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_compositor, - WL_COMPOSITOR_CREATE_REGION, - s_Wl.regionInterface, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_compositor), - 0, - NULL); - - return (struct wl_region *) id; -} - -static inline void wlRegionAdd( - struct wl_region *wl_region, - int32_t x, - int32_t y, - int32_t width, - int32_t height) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_region, - WL_REGION_ADD, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_region), - 0, - x, - y, - width, - height); -} - -static inline void wlSurfaceSetOpaqueRegion( - struct wl_surface *wl_surface, - struct wl_region *region) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_surface, - WL_SURFACE_SET_OPAQUE_REGION, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_surface), - 0, - region); -} - -static inline void wlRegionDestroy(struct wl_region *wl_region) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy *) wl_region, - WL_REGION_DESTROY, - NULL, - s_Wl.proxyGetVersion( - (struct wl_proxy *) wl_region), - WL_MARSHAL_FLAG_DESTROY); -} - -static void surfaceHandleEnter( - void* userData, - struct wl_surface* surface, - struct wl_output* output) -{ - WindowData* data = userData; - MonitorData* monitorData = findMonitorData((PalMonitor*)output); - if (!monitorData) { - return; - } - - // wayland sends multiple surface enter events - // if the surface spans multiple monitors - // we get all and return the highest dpi - // this assumes a surface can only span 4 monitors - // at the sametime but this might be wrong - // FIXME: check if we need more - - // wayland will trigger this event if the window gains focus - // so we check if the output is the same - SpanMonitor* span = nullptr; - if (data->monitorCount > 0) { - for (int i = 0; i < data->monitorCount; i++) { - if (data->monitors[i].monitor == (void*)output) { - // the monitor already exist in our array - // so we just update it - span = &data->monitors[i]; - span->dpi = monitorData->dpi; - break; - } - } - } - - if (span == nullptr) { - // new entry - span = &data->monitors[data->monitorCount]; - span->monitor = output; - span->dpi = monitorData->dpi; - data->monitorCount++; - } - - if (data->dpi == 0) { - // this is triggered when the window is created - // we cache the DPI and skip the event - data->dpi = monitorData->dpi; - return; - } - - // the code below should be skipped if users are not - // interested in DPI changed events - PalDispatchMode mode = PAL_DISPATCH_NONE; - PalEventType type = PAL_EVENT_MONITOR_DPI_CHANGED; - if (!s_Video.eventDriver) { - return; - } - - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode == PAL_DISPATCH_NONE) { - return; - } - - // get the highest dpi and check if the it has changed - int maxDPI = 96; // baseline - for (int i = 0; i < data->monitorCount; i++) { - if (!data->monitors[i].monitor) { - // not a valid index. continue - continue; - } - - if (data->monitors[i].dpi > maxDPI) { - // new highest - maxDPI = data->monitors[i].dpi; - } - } - - if (maxDPI != data->dpi) { - data->dpi = maxDPI; - - PalEvent event = {0}; - event.type = type; - event.data = maxDPI; - event.data2 = palPackPointer((void*)data->window); - palPushEvent(driver, &event); - } -} - -static void surfaceHandleLeave( - void* userData, - struct wl_surface* surface, - struct wl_output* output) -{ - // remove the monitor from our span monitor list - WindowData* data = userData; - MonitorData* monitorData = findMonitorData((PalMonitor*)output); - if (!monitorData) { - return; - } - - for (int i = 0; i < data->monitorCount; i++) { - if (data->monitors[i].monitor == (void*)output) { - // found our monitor - // we might want to pack the array but its just 4 monitors - // so we leave it like that - data->monitors[i].monitor = nullptr; - data->monitorCount--; - break; - } - } -} - -static struct wl_surface_listener surfaceListener = { - .enter = surfaceHandleEnter, - .leave = surfaceHandleLeave -}; - -static void pointerHandleEnter( - void* userData, - struct wl_pointer* pointer, - uint32_t serial, - struct wl_surface* surface, - wl_fixed_t surface_x, - wl_fixed_t surface_y) -{ - WindowData* data = findWindowData((PalWindow*)surface); - if (!data) { - return; - } - - if (data->cursor) { - // our window - WaylandCursor* cursor = data->cursor; - wlPointerSetCursor( - pointer, - serial, - cursor->surface, - cursor->hotspotX, - cursor->hotspotY); - } - - // cache the surface the pointer is currently on - s_Wl.pointerSurface = surface; -} - -static void pointerHandleLeave( - void* userData, - struct wl_pointer* pointer, - uint32_t serial, - struct wl_surface* surface) -{ - if (s_Wl.pointerSurface == surface) { - s_Wl.pointerSurface == nullptr; - } -} - -static void pointerHandleMotion( - void* userData, - struct wl_pointer* pointer, - uint32_t time, - wl_fixed_t surface_x, - wl_fixed_t surface_y) -{ - int x = wl_fixed_to_int(surface_x); - int y = wl_fixed_to_int(surface_y); - const int dx = x - s_Mouse.lastX; - const int dy = y - s_Mouse.lastY; - - PalDispatchMode mode = PAL_DISPATCH_NONE; - PalWindow* window = (PalWindow*)s_Wl.pointerSurface; - if (s_Video.eventDriver && window) { - // we only push a mouse move only if we are on a window - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MOUSE_MOVE; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(x, y); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - - // push a mouse delta event - type = PAL_EVENT_MOUSE_DELTA; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(dx, dy); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - - s_Mouse.lastX = x; - s_Mouse.lastY = y; - s_Mouse.dx = dx; - s_Mouse.dy = dy; -} - -static void pointerHandleButton( - void* userData, - struct wl_pointer* pointer, - uint32_t serial, - uint32_t time, - uint32_t button, - uint32_t state) -{ - PalWindow* window = nullptr; - if (s_Wl.pointerSurface) { - window = (PalWindow*)s_Wl.pointerSurface; - - } else { - // cannot recieve events without a focused surface - return; - } - - bool pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; - PalMouseButton _button = 0; - PalEventType type = PAL_EVENT_MOUSE_BUTTONUP; - - if (button == BTN_LEFT) { - _button = PAL_MOUSE_BUTTON_LEFT; - - } else if (button == BTN_RIGHT) { - _button = PAL_MOUSE_BUTTON_RIGHT; - - } else if (button == BTN_MIDDLE) { - _button = PAL_MOUSE_BUTTON_MIDDLE; - - } else if (button == BTN_SIDE) { - _button = PAL_MOUSE_BUTTON_X1; - - } else if (button == BTN_EXTRA) { - _button = PAL_MOUSE_BUTTON_X2; - } - - if (pressed) { - type = PAL_EVENT_MOUSE_BUTTONDOWN; - } - - s_Mouse.state[_button] = pressed; - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - - // since we are not drawing decorations for users - // they need the serial in order to draw their decorations - // we put the serial at the upper 32 so ABI is preserved - event.data = palPackUint32(_button, serial); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } -} - -static void pointerHandleAxis( - void* userData, - struct wl_pointer* pointer, - uint32_t time, - uint32_t axis, - wl_fixed_t value) -{ - double delta = wl_fixed_to_double(value); - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - s_Mouse.tmpScrollX += delta; - s_Mouse.accumScrollX += delta; - - } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - s_Mouse.tmpScrollY += delta; - s_Mouse.accumScrollY += delta; - } - - s_Mouse.pendingScroll = true; -} - -static void pointerHandleAxisDiscrete( - void* userData, - struct wl_pointer* pointer, - uint32_t axis, - int32_t discrete) -{ - if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { - s_Mouse.tmpScrollX += discrete; - s_Mouse.accumScrollX += discrete; - - } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { - s_Mouse.tmpScrollY += discrete; - s_Mouse.accumScrollY += discrete; - } - - s_Mouse.pendingScroll = true; -} - -static void pointerHandleFrame( - void* userData, - struct wl_pointer* pointer) -{ - if (!s_Mouse.pendingScroll) { - // no wheel event - return; - } - - PalWindow* window = nullptr; - if (s_Wl.pointerSurface) { - window = (PalWindow*)s_Wl.pointerSurface; - - } else { - // cannot recieve events without a focused surface - return; - } - - const int dx = (int)s_Mouse.accumScrollX; - const int dy = (int)s_Mouse.accumScrollY; - if (s_Video.eventDriver) { - PalEventType type = PAL_EVENT_MOUSE_WHEEL; - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(dx, dy); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - - s_Mouse.WheelX = dx; - s_Mouse.WheelY = dy; - s_Mouse.accumScrollX -= dx; - s_Mouse.accumScrollY -= dy; - s_Mouse.pendingScroll = false; -} - -static void pointerHandleAxisSource( - void* userData, - struct wl_pointer* pointer, - uint32_t axis_source) -{ - -} - -static void pointerHandleAxisStop( - void* userData, - struct wl_pointer* pointer, - uint32_t time, - uint32_t axis) -{ - -} - -static void keyboardHandleEnter( - void* userData, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface, - struct wl_array* keys) -{ - // cache the surface the keyboard is currently on - s_Wl.keyboardSurface = surface; -} - -static void keyboardHandleLeave( - void* userData, - struct wl_keyboard* keyboard, - uint32_t serial, - struct wl_surface* surface) -{ - if (s_Wl.keyboardSurface == surface) { - s_Wl.keyboardSurface == nullptr; - } -} - -static void keyboardHandleRemap( - void* userData, - struct wl_keyboard* keyboard, - uint32_t format, - int32_t fd, - uint32_t size) -{ - if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { - close(fd); - return; - } - - struct xkb_keymap* keymap = nullptr; - struct xkb_state* state = nullptr; - - char* keymapStr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); - if (keymapStr == MAP_FAILED) { - close(fd); - return; - } - - keymap = s_Wl.xkbKeymapNewFromString( - s_Wl.inputContext, - keymapStr, - XKB_KEYMAP_FORMAT_TEXT_V1, - XKB_KEYMAP_COMPILE_NO_FLAGS); - - if (!keymap) { - return; - } - - munmap(keymapStr, size); - close(fd); - - state = s_Wl.xkbStateNew(keymap); - if (!state) { - return; - } - - // check if we have old keymap and state - if (s_Wl.state) { - s_Wl.xkbStateUnref(s_Wl.state); - s_Wl.xkbKeymapUnref(s_Wl.keymap); - } - - s_Wl.state = state; - s_Wl.keymap = keymap; - s_Keyboard.frequency = palGetPerformanceFrequency(); -} - -static void keyboardHandleKey( - void* userData, - struct wl_keyboard* keyboard, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state) -{ - PalWindow* window = nullptr; - if (s_Wl.keyboardSurface) { - window = (PalWindow*)s_Wl.keyboardSurface; - - } else { - // cannot recieve events without a focused surface - return; - } - - PalScancode scancode = 0; - PalKeycode keycode = 0; - bool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED); - PalEventType type = PAL_EVENT_KEYUP; - PalDispatchMode mode = PAL_DISPATCH_NONE; - xkb_keysym_t keySym = s_Wl.xkbStateKeyGetOneSym(s_Wl.state, key + 8); - - // special handling - if (key == 119) { - scancode = PAL_SCANCODE_PAUSE; - } else if (key == 107) { - scancode = PAL_SCANCODE_END; - } else if (key == 103) { - scancode = PAL_SCANCODE_UP; - } else if (key == 102) { - scancode = PAL_SCANCODE_HOME; - - } else { - scancode = s_Keyboard.scancodes[key]; - } - - // printable and text input keys are from the range - // 32 (PAL_KEYCODE_SPACE) and 122 (PAL_KEYCODE_Z) - // The rest are almost the same as their scancode - // Maybe there will be a layout that makes this wrong - // but for now this works - if (keySym >= XKB_KEY_space && keySym <= XKB_KEY_z) { - // a printable or input key - keycode = s_Keyboard.keycodes[keySym]; - - } else { - // Since PalKeycode and PalScancode have the same integers - // we can make a direct cast without a table - // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) - keycode = (PalKeycode)(Uint32)scancode; - } - - // If we got a keySym but its not mapped into our keycode array - // we do a direct cast as well - if (keycode == PAL_KEYCODE_UNKNOWN) { - keycode = (PalKeycode)(Uint32)scancode; - } - - s_Keyboard.scancodeState[scancode] = pressed; - s_Keyboard.keycodeState[keycode] = pressed; - - // check for key repeats - if (pressed) { - if (s_Wl.xkbKeymapKeyRepeats(s_Wl.keymap, key + 8)) { - s_Keyboard.repeatKey = keycode; - s_Keyboard.repeatScancode = scancode; - s_Keyboard.timer = getTime() + s_Keyboard.repeatDelay; - - } else { - s_Keyboard.repeatKey = 0; - } - - type = PAL_EVENT_KEYDOWN; - - } else { - // key release - if (s_Keyboard.repeatKey == keycode) { - s_Keyboard.repeatKey = 0; - } - } - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - - // check for char event if enabled - type = PAL_EVENT_KEYCHAR; - mode = palGetEventDispatchMode(driver, type); - if (mode == PAL_DISPATCH_NONE) { - return; - } - - Uint32 codepoint = s_Wl.xkbKeysymToUtf32(keySym); - if (codepoint <= 0) { - return; - } - - PalEvent event = {0}; - event.type = type; - event.data = codepoint; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } -} - -static void keyboardHandleRepeatInfo( - void* userData, - struct wl_keyboard* keyboard, - int32_t rate, - int32_t delay) -{ - if (s_Wl.keyboard == keyboard) { - s_Keyboard.repeatDelay = delay; - s_Keyboard.repeatRate = rate; - - } else { - if (s_Keyboard.repeatDelay == 0) { - s_Keyboard.repeatDelay = 500; - s_Keyboard.repeatRate = 30; - } - } -} - -static void keyboardHandleModifiers( - void* userData, - struct wl_keyboard* keyboard, - uint32_t serial, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group) -{ - if (s_Wl.state) { - s_Wl.xkbStateUpdateMask( - s_Wl.state, - mods_depressed, - mods_latched, - mods_locked, - group, - 0, - 0); - } -} - -static struct wl_pointer_listener pointerListener = { - .enter = pointerHandleEnter, - .leave = pointerHandleLeave, - .motion = pointerHandleMotion, - .button = pointerHandleButton, - .axis = pointerHandleAxis, - .axis_discrete = pointerHandleAxisDiscrete, - .frame = pointerHandleFrame, - .axis_source = pointerHandleAxisSource, - .axis_stop = pointerHandleAxisStop -}; - -static struct wl_keyboard_listener keyboardListener = { - .enter = keyboardHandleEnter, - .leave = keyboardHandleLeave, - .keymap = keyboardHandleRemap, - .key = keyboardHandleKey, - .repeat_info = keyboardHandleRepeatInfo, - .modifiers = keyboardHandleModifiers -}; - -static void seatHandleCapabilities( - void* userData, - struct wl_seat* seat, - enum wl_seat_capability caps) -{ - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - s_Wl.keyboard = wlSeatGetKeyboard(seat); - wlKeyboardAddListener(s_Wl.keyboard, &keyboardListener, nullptr); - } - - if (caps & WL_SEAT_CAPABILITY_POINTER) { - s_Wl.pointer = wlSeatGetPointer(seat); - wlPointerAddListener(s_Wl.pointer, &pointerListener, nullptr); - } -} - -static void seatHandleName( - void* userData, - struct wl_seat* seat, - const char* name) -{ - -} - -static struct wl_seat_listener seatListener = { - .capabilities = seatHandleCapabilities, - .name = seatHandleName -}; - -#endif // PAL_HAS_WAYLAND -#pragma endregion - -#pragma region Xdg-Shell-Protocol -#if PAL_HAS_WAYLAND - -struct xdg_wm_base; -struct xdg_surface; -struct xdg_toplevel; - -// forward declare -static struct wl_buffer* createShmBuffer( - int width, - int height, - const Uint8* pixels, - bool cursor); - -const struct wl_interface xdg_popup_interface; -const struct wl_interface xdg_positioner_interface; -const struct wl_interface xdg_surface_interface; -const struct wl_interface xdg_toplevel_interface; - -struct xdg_wm_base_listener { - void (*ping)(void*, struct xdg_wm_base*, uint32_t); -}; - -struct xdg_surface_listener { - void (*configure)(void*, struct xdg_surface*, uint32_t); -}; - -struct xdg_toplevel_listener { - // clang-format off - void (*configure)(void*, struct xdg_toplevel*, int32_t, int32_t, struct wl_array*); - void (*close)(void*, struct xdg_toplevel*); - void (*configure_bounds)(void*, struct xdg_toplevel*, int32_t, int32_t); - void (*wm_capabilities)(void*, struct xdg_toplevel*, struct wl_array*); - // clang-format on -}; - -static inline void xdgWmBasePong( - struct xdg_wm_base* xdg_wm_base, - uint32_t serial) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_wm_base, - 3, // XDG_WM_BASE_PONG - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), - 0, - serial); -} - -static inline int xdgWmBaseAddListener( - struct xdg_wm_base* xdg_wm_base, - const struct xdg_wm_base_listener* listener, - void* data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy*)xdg_wm_base, - (void (**)(void))listener, - data); -} - -static inline struct xdg_surface* xdgWmBaseGetXdgSurface( - struct xdg_wm_base* xdg_wm_base, - struct wl_surface* surface) -{ - struct wl_proxy* id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_wm_base, - 2, // XDG_WM_BASE_GET_XDG_SURFACE, - &xdg_surface_interface, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), - 0, - NULL, - surface); - - return (struct xdg_surface*)id; -} - -static inline struct xdg_toplevel* -xdgSurfaceGetToplevel(struct xdg_surface* xdg_surface) -{ - struct wl_proxy* id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_surface, - 1, // XDG_SURFACE_GET_TOPLEVEL, - &xdg_toplevel_interface, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), - 0, - NULL); - - return (struct xdg_toplevel*)id; -} - -static inline void xdgSurfaceAckConfigure( - struct xdg_surface* xdg_surface, - uint32_t serial) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_surface, - 4, // XDG_SURFACE_ACK_CONFIGURE - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), - 0, - serial); -} - -static void wmBaseHandlePing( - void* data, - struct xdg_wm_base* base, - uint32_t serial) -{ - xdgWmBasePong(base, serial); -} - -static void xdgSurfaceHandleConfigure( - void* data, - struct xdg_surface* surface, - uint32_t serial) -{ - WindowData* winData = (WindowData*)data; - xdgSurfaceAckConfigure(surface, serial); - - // push and resolve any pending events - if (!winData->skipConfigure) { - if (winData->pushConfigureEvent) { - if (winData->eglWindow) { - s_Wl.eglWindowResize( - winData->eglWindow, - winData->w, - winData->h, - 0, - 0); - - } else { - // create a new buffer with the new size - struct wl_buffer* buffer = nullptr; - buffer = - createShmBuffer(winData->w, winData->h, nullptr, false); - if (!buffer) { - return; - } - - struct wl_surface* _surface = nullptr; - _surface = (struct wl_surface*)winData->window; - - wlSurfaceAttach(_surface, buffer, 0, 0); - wlSurfaceDamageBuffer(_surface, 0, 0, winData->w, winData->h); - wlSurfaceCommit(_surface); - - // destroy old buffer - wlBufferDestroy(winData->buffer); - winData->buffer = buffer; - } - - // push a window resize event - if (s_Video.eventDriver) { - PalEventType type = PAL_EVENT_WINDOW_SIZE; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(s_Video.eventDriver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(winData->w, winData->h); - event.data2 = palPackPointer(winData->window); - palPushEvent(s_Video.eventDriver, &event); - } - } - - winData->pushConfigureEvent = false; - } - } - - // pending state - if (!winData->skipState) { - if (!winData->pushStateEvent) { - return; - } - - // push a window state event - // we dont recreate buffers over here - // since we already create the buffer with the new size - if (s_Video.eventDriver) { - PalEventType type = PAL_EVENT_WINDOW_STATE; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(s_Video.eventDriver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = winData->state; - event.data2 = palPackPointer(winData->window); - palPushEvent(s_Video.eventDriver, &event); - } - } - - winData->pushStateEvent = false; - } -} - -static void xdgToplevelHandleConfigure( - void* data, - struct xdg_toplevel* toplevel, - int32_t width, - int32_t height, - struct wl_array* states) -{ - WindowData* winData = (WindowData*)data; - uint32_t* state; - bool activated = false; - wl_array_for_each(state, states) - { - // we need only maximized - if (*state == 1) { // XDG_TOPLEVEL_STATE_MAXIMIZED - if (winData->state != PAL_WINDOW_STATE_MAXIMIZED) { - winData->state = PAL_WINDOW_STATE_MAXIMIZED; - winData->pushStateEvent = true; - } - - } else if (*state == 4) { // XDG_TOPLEVEL_STATE_ACTIVATED - activated = true; - } - } - - if (width > 0 && height > 0) { - if (width != winData->w || height != winData->h) { - // size change - winData->pushConfigureEvent = true; - } - - winData->w = width; - winData->h = height; - } - - if (activated && !winData->focused) { - // focus gained - winData->focused = true; - - } else if (!activated && winData->focused) { - // focus lost - winData->focused = false; - - } else { - // discard double focus gained and double focus lost - return; - } - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode mode = PAL_DISPATCH_NONE; - PalEventType type = PAL_EVENT_WINDOW_FOCUS; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = winData->focused; - event.data2 = palPackPointer(winData->window); - palPushEvent(driver, &event); - } - } -} - -static void xdgToplevelHandleClose( - void* data, - struct xdg_toplevel* toplevel) -{ - WindowData* winData = (WindowData*)data; - if (s_Video.eventDriver) { - PalEventType type = PAL_EVENT_WINDOW_CLOSE; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(s_Video.eventDriver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer(winData->window); - palPushEvent(s_Video.eventDriver, &event); - } - } -} - -static inline int xdgSurfaceAddListener( - struct xdg_surface* xdg_surface, - const struct xdg_surface_listener* listener, - void* data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy*)xdg_surface, - (void (**)(void))listener, - data); -} - -static inline void xdgSurfaceDestroy(struct xdg_surface* xdg_surface) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_surface, - 0, // XDG_SURFACE_DESTROY - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline void xdgToplevelDestroy(struct xdg_toplevel* xdg_toplevel) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 0, // XDG_TOPLEVEL_DESTROY - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline void xdgToplevelSetTitle( - struct xdg_toplevel* xdg_toplevel, - const char* title) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 2, // XDG_TOPLEVEL_SET_TITLE - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0, - title); -} - -static inline void xdgToplevelSetMaximized(struct xdg_toplevel* xdg_toplevel) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 9, // XDG_TOPLEVEL_SET_MAXIMIZED - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0); -} - -static inline void xdgToplevelSetMinimized(struct xdg_toplevel* xdg_toplevel) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 13, // XDG_TOPLEVEL_SET_MINIMIZED - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0); -} - -static inline int xdgToplevelAddListener( - struct xdg_toplevel* xdg_toplevel, - const struct xdg_toplevel_listener* listener, - void* data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy*)xdg_toplevel, - (void (**)(void))listener, - data); -} - -static inline void xdgToplevelSetMinSize( - struct xdg_toplevel* xdg_toplevel, - int32_t width, - int32_t height) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 8, // XDG_TOPLEVEL_SET_MIN_SIZE - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0, - width, - height); -} - -static inline void xdgToplevelSetMaxSize( - struct xdg_toplevel* xdg_toplevel, - int32_t width, - int32_t height) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 7, // XDG_TOPLEVEL_SET_MAX_SIZE - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0, - width, - height); -} - -static inline void xdgToplevelSetAppId( - struct xdg_toplevel* xdg_toplevel, - const char* app_id) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 3, // XDG_TOPLEVEL_SET_APP_ID - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0, - app_id); -} - -static inline void xdgToplevelUnsetMaximized(struct xdg_toplevel* xdg_toplevel) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)xdg_toplevel, - 10, // XDG_TOPLEVEL_UNSET_MAXIMIZED - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), - 0); -} - -static const struct wl_interface* xdg_shell_types[26]; - -static const struct wl_message xdg_wm_base_requests[] = { - {"destroy", "", xdg_shell_types + 0}, - {"create_positioner", "n", xdg_shell_types + 4}, - {"get_xdg_surface", "no", xdg_shell_types + 5}, - {"pong", "u", xdg_shell_types + 0}, -}; - -static const struct wl_message xdg_wm_base_events[] = { - {"ping", "u", xdg_shell_types + 0}, -}; - -const struct wl_interface xdg_wm_base_interface = { - "xdg_wm_base", - 6, - 4, - xdg_wm_base_requests, - 1, - xdg_wm_base_events, -}; - -static const struct wl_message xdg_positioner_requests[] = { - {"destroy", "", xdg_shell_types + 0}, - {"set_size", "ii", xdg_shell_types + 0}, - {"set_anchor_rect", "iiii", xdg_shell_types + 0}, - {"set_anchor", "u", xdg_shell_types + 0}, - {"set_gravity", "u", xdg_shell_types + 0}, - {"set_constraint_adjustment", "u", xdg_shell_types + 0}, - {"set_offset", "ii", xdg_shell_types + 0}, - {"set_reactive", "3", xdg_shell_types + 0}, - {"set_parent_size", "3ii", xdg_shell_types + 0}, - {"set_parent_configure", "3u", xdg_shell_types + 0}, -}; - -const struct wl_interface xdg_positioner_interface = { - "xdg_positioner", - 6, - 10, - xdg_positioner_requests, - 0, - NULL, -}; - -static const struct wl_message xdg_surface_requests[] = { - {"destroy", "", xdg_shell_types + 0}, - {"get_toplevel", "n", xdg_shell_types + 7}, - {"get_popup", "n?oo", xdg_shell_types + 8}, - {"set_window_geometry", "iiii", xdg_shell_types + 0}, - {"ack_configure", "u", xdg_shell_types + 0}, -}; - -static const struct wl_message xdg_surface_events[] = { - {"configure", "u", xdg_shell_types + 0}, -}; - -const struct wl_interface xdg_surface_interface = { - "xdg_surface", - 6, - 5, - xdg_surface_requests, - 1, - xdg_surface_events, -}; - -static const struct wl_message xdg_toplevel_requests[] = { - {"destroy", "", xdg_shell_types + 0}, - {"set_parent", "?o", xdg_shell_types + 11}, - {"set_title", "s", xdg_shell_types + 0}, - {"set_app_id", "s", xdg_shell_types + 0}, - {"show_window_menu", "ouii", xdg_shell_types + 12}, - {"move", "ou", xdg_shell_types + 16}, - {"resize", "ouu", xdg_shell_types + 18}, - {"set_max_size", "ii", xdg_shell_types + 0}, - {"set_min_size", "ii", xdg_shell_types + 0}, - {"set_maximized", "", xdg_shell_types + 0}, - {"unset_maximized", "", xdg_shell_types + 0}, - {"set_fullscreen", "?o", xdg_shell_types + 21}, - {"unset_fullscreen", "", xdg_shell_types + 0}, - {"set_minimized", "", xdg_shell_types + 0}, -}; - -static const struct wl_message xdg_toplevel_events[] = { - {"configure", "iia", xdg_shell_types + 0}, - {"close", "", xdg_shell_types + 0}, - {"configure_bounds", "4ii", xdg_shell_types + 0}, - {"wm_capabilities", "5a", xdg_shell_types + 0}, -}; - -const struct wl_interface xdg_toplevel_interface = { - "xdg_toplevel", - 6, - 14, - xdg_toplevel_requests, - 4, - xdg_toplevel_events, -}; - -static const struct wl_message xdg_popup_requests[] = { - {"destroy", "", xdg_shell_types + 0}, - {"grab", "ou", xdg_shell_types + 22}, - {"reposition", "3ou", xdg_shell_types + 24}, -}; - -static const struct wl_message xdg_popup_events[] = { - {"configure", "iiii", xdg_shell_types + 0}, - {"popup_done", "", xdg_shell_types + 0}, - {"repositioned", "3u", xdg_shell_types + 0}, -}; - -const struct wl_interface xdg_popup_interface = { - "xdg_popup", - 6, - 3, - xdg_popup_requests, - 3, - xdg_popup_events, -}; - -static void setupXdgShellProtocol() -{ - xdg_shell_types[0] = NULL; - xdg_shell_types[1] = NULL; - xdg_shell_types[2] = NULL; - xdg_shell_types[3] = NULL; - xdg_shell_types[4] = &xdg_positioner_interface; - xdg_shell_types[5] = &xdg_surface_interface; - xdg_shell_types[6] = s_Wl.surfaceInterface; - xdg_shell_types[7] = &xdg_toplevel_interface; - xdg_shell_types[8] = &xdg_popup_interface; - xdg_shell_types[9] = &xdg_surface_interface; - xdg_shell_types[10] = &xdg_positioner_interface; - xdg_shell_types[11] = &xdg_toplevel_interface; - xdg_shell_types[12] = s_Wl.seatInterface; - xdg_shell_types[13] = NULL; - xdg_shell_types[14] = NULL; - xdg_shell_types[15] = NULL; - xdg_shell_types[16] = s_Wl.seatInterface; - xdg_shell_types[17] = NULL; - xdg_shell_types[18] = s_Wl.seatInterface; - xdg_shell_types[19] = NULL; - xdg_shell_types[20] = NULL; - xdg_shell_types[21] = s_Wl.outputInterface; - xdg_shell_types[22] = s_Wl.seatInterface; - xdg_shell_types[23] = NULL; - xdg_shell_types[24] = &xdg_positioner_interface; - xdg_shell_types[25] = NULL; -} - -static const struct xdg_wm_base_listener wmBaseListener = { - .ping = wmBaseHandlePing}; - -static const struct xdg_surface_listener xdgSurfaceListener = { - .configure = xdgSurfaceHandleConfigure}; - -static const struct xdg_toplevel_listener xdgToplevelListener = { - .configure = xdgToplevelHandleConfigure, - .close = xdgToplevelHandleClose, - .configure_bounds = nullptr, - .wm_capabilities = nullptr}; - -#endif // PAL_HAS_WAYLAND -#pragma endregion - -#pragma region Zxdg-Decoraion_Manager-V1 -#if PAL_HAS_WAYLAND - -struct xdg_toplevel; -struct zxdg_decoration_manager_v1; -struct zxdg_toplevel_decoration_v1; - -struct zxdg_toplevel_decoration_v1_listener { - void (*configure)( - void*, - struct zxdg_toplevel_decoration_v1*, - uint32_t); -}; - -const struct wl_interface zxdg_decoration_manager_v1_interface; -const struct wl_interface zxdg_toplevel_decoration_v1_interface; - -static inline void zxdgDecorationManagerV1Destroy( - struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)zxdg_decoration_manager_v1, - 0, // ZXDG_DECORATION_MANAGER_V1_DESTROY - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline struct zxdg_toplevel_decoration_v1* zxdgGetToplevelDecoration( - struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1, - struct xdg_toplevel* toplevel) -{ - struct wl_proxy* id; - id = s_Wl.proxyMarshalFlags( - (struct wl_proxy*)zxdg_decoration_manager_v1, - 1, // ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, - &zxdg_toplevel_decoration_v1_interface, - s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), - 0, - NULL, - toplevel); - - return (struct zxdg_toplevel_decoration_v1*)id; -} - -static inline int zxdgToplevelDecorationV1AddListener( - struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, - const struct zxdg_toplevel_decoration_v1_listener* listener, - void* data) -{ - return s_Wl.proxyAddListener( - (struct wl_proxy*)zxdg_toplevel_decoration_v1, - (void (**)(void))listener, - data); -} - -static inline void zxdgToplevelDecorationV1Destroy( - struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)zxdg_toplevel_decoration_v1, - 0, // ZXDG_TOPLEVEL_DECORATION_V1_DESTROY - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), - WL_MARSHAL_FLAG_DESTROY); -} - -static inline void zxdgToplevelDecorationV1SetMode( - struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, - uint32_t mode) -{ - s_Wl.proxyMarshalFlags( - (struct wl_proxy*)zxdg_toplevel_decoration_v1, - 1, // ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, - NULL, - s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), - 0, - mode); -} - -static const struct wl_interface* xdg_decoration_unstable_v1_types[] = { - NULL, - &zxdg_toplevel_decoration_v1_interface, - &xdg_toplevel_interface, -}; - -static const struct wl_message zxdg_decoration_manager_v1_requests[] = { - {"destroy", "", xdg_decoration_unstable_v1_types + 0}, - {"get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1}, -}; - -const struct wl_interface zxdg_decoration_manager_v1_interface = { - "zxdg_decoration_manager_v1", - 1, - 2, - zxdg_decoration_manager_v1_requests, - 0, - NULL, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { - {"destroy", "", xdg_decoration_unstable_v1_types + 0}, - {"set_mode", "u", xdg_decoration_unstable_v1_types + 0}, - {"unset_mode", "", xdg_decoration_unstable_v1_types + 0}, -}; - -static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { - {"configure", "u", xdg_decoration_unstable_v1_types + 0}, -}; - -const struct wl_interface zxdg_toplevel_decoration_v1_interface = { - "zxdg_toplevel_decoration_v1", - 1, - 3, - zxdg_toplevel_decoration_v1_requests, - 1, - zxdg_toplevel_decoration_v1_events, -}; - -void zxdgDecorationHandleConfigure( - void* data, - struct zxdg_toplevel_decoration_v1* dec, - uint32_t mode) -{ - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; - PalEventType type = PAL_EVENT_WINDOW_DECORATION_MODE; - dispatchMode = palGetEventDispatchMode(driver, type); - - if (dispatchMode != PAL_DISPATCH_NONE) { - PalDecorationMode decorMode = PAL_DECORATION_MODE_SERVER_SIDE; - if (mode == 0 || mode == 1) { - // client side decoration - decorMode = PAL_DECORATION_MODE_CLIENT_SIDE; - } - - PalEvent event = {0}; - event.type = type; - event.data = decorMode; - event.data2 = palPackPointer(data); - palPushEvent(driver, &event); - } - } -} - -static struct zxdg_toplevel_decoration_v1_listener decorationListener = { - .configure = zxdgDecorationHandleConfigure}; - -#endif // PAL_HAS_WAYLAND -#pragma endregion - -// ================================================== -// Internal API -// ================================================== - -static int compareModes( - const void* a, - const void* b) -{ - const PalMonitorMode* mode1 = (const PalMonitorMode*)a; - const PalMonitorMode* mode2 = (const PalMonitorMode*)b; - - // compare fields - if (mode1->width != mode2->width) { - return mode1->width - mode2->width; - } - - if (mode1->height != mode2->height) { - return mode1->height - mode2->height; - } - - if (mode1->refreshRate != mode2->refreshRate) { - return mode1->refreshRate - mode2->refreshRate; - } - - if (mode1->bpp != mode2->bpp) { - return mode1->bpp - mode2->bpp; - } -} - -static WindowData* getFreeWindowData() -{ - for (int i = 0; i < s_Video.maxWindowData; ++i) { - if (!s_Video.windowData[i].used) { - s_Video.windowData[i].used = true; - return &s_Video.windowData[i]; - } - } - - // resize the data array - // It is rare for a user to create and manage - // 32 windows at the same time - WindowData* data = nullptr; - int count = s_Video.maxWindowData * 2; // double the size - int freeIndex = s_Video.maxWindowData + 1; - data = palAllocate(s_Video.allocator, sizeof(WindowData) * count, 0); - if (data) { - memcpy( - data, - s_Video.windowData, - s_Video.maxWindowData * sizeof(WindowData)); - - palFree(s_Video.allocator, s_Video.windowData); - s_Video.windowData = data; - s_Video.maxWindowData = count; - - s_Video.windowData[freeIndex].used = true; - return &s_Video.windowData[freeIndex]; - } - return nullptr; -} - -static WindowData* findWindowData(PalWindow* window) -{ - for (int i = 0; i < s_Video.maxWindowData; ++i) { - if (s_Video.windowData[i].used && - s_Video.windowData[i].window == window) { - return &s_Video.windowData[i]; - } - } - return nullptr; -} - -static void resetMonitorData() -{ - memset( - s_Video.monitorData, - 0, - s_Video.maxMonitorData * sizeof(MonitorData)); -} - -static MonitorData* getFreeMonitorData() -{ - for (int i = 0; i < s_Video.maxMonitorData; ++i) { - if (!s_Video.monitorData[i].used) { - s_Video.monitorData[i].used = true; - return &s_Video.monitorData[i]; - } - } - - // resize the data array - // this will almost not reach here since most setups are 1-4 monitors - MonitorData* data = nullptr; - int count = s_Video.maxMonitorData * 2; // double the size - int freeIndex = s_Video.maxMonitorData + 1; - data = palAllocate(s_Video.allocator, sizeof(MonitorData) * count, 0); - if (data) { - memcpy( - data, - s_Video.monitorData, - s_Video.maxMonitorData * sizeof(MonitorData)); - - palFree(s_Video.allocator, s_Video.monitorData); - s_Video.monitorData = data; - s_Video.maxWindowData = count; - - s_Video.monitorData[freeIndex].used = true; - return &s_Video.monitorData[freeIndex]; - } - return nullptr; -} - -static MonitorData* findMonitorData(PalMonitor* monitor) -{ - for (int i = 0; i < s_Video.maxMonitorData; ++i) { - if (s_Video.monitorData[i].used && - s_Video.monitorData[i].monitor == monitor) { - return &s_Video.monitorData[i]; - } - } - return nullptr; -} - -static void freeMonitorData(PalMonitor* monitor) -{ - for (int i = 0; i < s_Video.maxMonitorData; ++i) { - if (s_Video.monitorData[i].used && - s_Video.monitorData[i].monitor == monitor) { - s_Video.monitorData[i].used = false; - } - } -} - -static void createScancodeTable() -{ - // Letters - s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; - s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; - s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; - s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; - s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; - s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; - s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; - s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; - s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; - s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; - s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; - s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; - s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; - s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; - s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; - s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; - s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; - s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; - s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; - s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; - s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; - s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; - s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; - s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; - s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; - s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; - - // Numbers (top row) - s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; - s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; - s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; - s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; - s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; - s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; - s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; - s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; - s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; - s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; - - // Function - s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; - s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; - s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; - s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; - s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; - s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; - s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; - s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; - s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; - s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; - s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; - s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; - - // Control - s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; - s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; - s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; - s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; - s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; - s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; - s_Keyboard.scancodes[0x045] = PAL_SCANCODE_NUMLOCK; - s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; - s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; - s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; - s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; - s_Keyboard.scancodes[0x061] = PAL_SCANCODE_RCTRL; - s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; - s_Keyboard.scancodes[0x064] = PAL_SCANCODE_RALT; - - // Arrows - s_Keyboard.scancodes[0x069] = PAL_SCANCODE_LEFT; - s_Keyboard.scancodes[0x06A] = PAL_SCANCODE_RIGHT; - s_Keyboard.scancodes[0x067] = PAL_SCANCODE_UP; - s_Keyboard.scancodes[0x06C] = PAL_SCANCODE_DOWN; - - // Navigation - s_Keyboard.scancodes[0x06E] = PAL_SCANCODE_INSERT; - s_Keyboard.scancodes[0x06F] = PAL_SCANCODE_DELETE; - s_Keyboard.scancodes[0x066] = PAL_SCANCODE_HOME; - s_Keyboard.scancodes[0x067] = PAL_SCANCODE_END; - s_Keyboard.scancodes[0x068] = PAL_SCANCODE_PAGEUP; - s_Keyboard.scancodes[0x06D] = PAL_SCANCODE_PAGEDOWN; - - // Keypad - s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; - s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; - s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; - s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; - s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; - s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; - s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; - s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; - s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; - s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; - s_Keyboard.scancodes[0x060] = PAL_SCANCODE_KP_ENTER; - s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; - s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; - s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; - s_Keyboard.scancodes[0x062] = PAL_SCANCODE_KP_DIVIDE; - s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; - - // Misc - s_Keyboard.scancodes[0x063] = PAL_SCANCODE_PRINTSCREEN; - s_Keyboard.scancodes[0x066] = PAL_SCANCODE_PAUSE; - s_Keyboard.scancodes[0x07F] = PAL_SCANCODE_MENU; - s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; - s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; - s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; - s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; - s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; - s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; - s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; - s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; - s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; - s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; - s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; - s_Keyboard.scancodes[0x07D] = PAL_SCANCODE_LSUPER; - s_Keyboard.scancodes[0x07E] = PAL_SCANCODE_RSUPER; -} - -void palSetLastPlatformError(Uint32 e); - -// ================================================== -// X11 API -// ================================================== - -#pragma region X11 API -#if PAL_HAS_X11 - -static PalResult glxBackend(const int index) -{ - // user choose GLX FBConfig backend - if (!s_X11.glxHandle) { - // Rare - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - // clang-format off - - int count = 0; - GLXFBConfig* configs = s_X11.glxGetFBConfigs( - s_X11.display, - s_X11.screen, - &count); - - GLXFBConfig fbConfig = configs[index]; - if (!fbConfig) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - // get a matching visual - XVisualInfo* visualInfo = s_X11.glxGetVisualFromFBConfig( - s_X11.display, - fbConfig); - - if (!visualInfo) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - s_X11.visualInfo = visualInfo; - return PAL_RESULT_SUCCESS; -} - -static PalResult eglXBackend(int index) -{ - // user choose EGL FBConfig backend - if (!s_Egl.handle) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLDisplay display = EGL_NO_DISPLAY; - display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_X11.display); - if (display == EGL_NO_DISPLAY) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint numConfigs = 0; - if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint configSize = sizeof(EGLConfig) * numConfigs; - EGLConfig* eglConfigs = palAllocate(s_Video.allocator, configSize, 0); - if (!eglConfigs) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); - EGLConfig config = eglConfigs[index]; - - // we get a visual info from the config - EGLint visualID; - s_Egl.eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &visualID); - if (visualID == 0) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - int numVisuals = 0; - XVisualInfo tmp; - tmp.visualid = visualID; - - // clang-format off - // get a matching visual info - XVisualInfo* visualInfo = s_X11.getVisualInfo( - s_X11.display, - VisualIDMask, - &tmp, - &numVisuals); - // clang-format on - - if (!visualInfo) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - s_X11.visualInfo = visualInfo; - palFree(s_Video.allocator, eglConfigs); - return PAL_RESULT_SUCCESS; -} - -static RRMode xFindMode( - XRRScreenResources* resources, - const PalMonitorMode* mode) -{ - for (int i = 0; i < resources->nmode; ++i) { - XRRModeInfo* info = &resources->modes[i]; - - double tmp = (double)info->hTotal * (double)info->vTotal; - double rate = (double)info->dotClock / tmp; - - // compare with width, height and refresh rate - if (info->width == mode->width && info->height == mode->height && - (Uint32)(rate + 0.5) == mode->refreshRate) { - return info->id; - } - } - return None; -} - -static void xCheckFeatures() -{ - // cache this atoms - X_INTERN(WM_DELETE_WINDOW); - X_INTERN(_NET_SUPPORTED); - X_INTERN(_NET_WM_STATE); - X_INTERN(_NET_WM_STATE_ABOVE); - X_INTERN(_NET_WM_STATE_MAXIMIZED_VERT); - X_INTERN(_NET_WM_STATE_MAXIMIZED_HORZ); - X_INTERN(_NET_WM_STATE_HIDDEN); - X_INTERN(_NET_WM_NAME); - X_INTERN(UTF8_STRING); - X_INTERN(_NET_WM_WINDOW_TYPE_UTILITY); - X_INTERN(_NET_WM_DESKTOP); - X_INTERN(_NET_WM_STATE_DEMANDS_ATTENTIONS); - X_INTERN(_NET_WM_WINDOW_OPACITY); - X_INTERN(_NET_WM_WINDOW_TYPE); - X_INTERN(_NET_WM_WINDOW_TYPE_SPLASH); - X_INTERN(_NET_WM_PID); - X_INTERN(_WM_CLASS); - X_INTERN(_NET_ACTIVE_WINDOW); - X_INTERN(_NET_WM_ICON); - - // check for support from the window manager - Atom type; - int format; - unsigned long count, bytesAfters; - Atom* supportedAtoms = nullptr; - s_X11.getWindowProperty( - s_X11.display, - s_X11.root, - s_X11Atoms._NET_SUPPORTED, - 0, - (~0L), - False, - XA_ATOM, - &type, - &format, - &count, - &bytesAfters, - (unsigned char**)&supportedAtoms); - - PalVideoFeatures features = 0; - PalVideoFeatures64 features64 = 0; - for (unsigned long i = 0; i < count; ++i) { - if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - } - - if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - } - - if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - } - - if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH) { - features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; - } - - if (supportedAtoms[i] == s_X11Atoms._NET_WM_NAME) { - s_X11Atoms.unicodeTitle = true; - } - - if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY) { - features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; - } - } - - // check for transparent windows - Atom compositor = s_X11.internAtom(s_X11.display, "_NET_WM_CM_S0", True); - if (compositor != None) { - Window owner = s_X11.getSelectionOwner(s_X11.display, compositor); - if (owner != None) { - features |= PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW; - } - } - - // general features - features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; - features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; - features |= PAL_VIDEO_FEATURE_MONITOR_SET_MODE; - features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_SIZE; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY; - - features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; - features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; - features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; - features |= PAL_VIDEO_FEATURE_WINDOW_GET_TITLE; - features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; - - // extended features - // old features - features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_MODE; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY; - - features64 |= PAL_VIDEO_FEATURE64_CLIP_CURSOR; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS; - features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_POS; - features64 |= PAL_VIDEO_FEATURE64_CURSOR_GET_POS; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY; - - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_ICON; - features64 |= PAL_VIDEO_FEATURE64_TOPMOST_WINDOW; - features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY; - features64 |= PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; - - s_Video.features = features; - s_Video.features64 = features64; - s_X11.free(supportedAtoms); -} - -static int xErrorHandler( - Display*, - XErrorEvent* e) -{ - // this is use for simple success and failure - s_X11.error = true; - return 0; -} - -static PalWindowState xQueryWindowState(Window xWin) -{ - Atom type; - int format; - unsigned long count, bytesAfter; - Atom* atoms = nullptr; - PalWindowState state = PAL_WINDOW_STATE_RESTORED; - - s_X11.getWindowProperty( - s_X11.display, - xWin, - s_X11Atoms._NET_WM_STATE, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &count, - &bytesAfter, - (unsigned char**)&atoms); - - for (unsigned int i = 0; i < count; i++) { - if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } - - if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } - - if (atoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - state = PAL_WINDOW_STATE_MINIMIZED; - } - } - - s_X11.free(atoms); - return state; -} - -static void xCacheMonitors() -{ - resetMonitorData(); - - XRRScreenResources* resources = nullptr; - resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - - for (int i = 0; i < resources->noutput; ++i) { - RROutput output = resources->outputs[i]; - // clang-format off - XRROutputInfo* info = s_X11.getOutputInfo(s_X11.display, resources, output); - // clang-format on - - if (info->connection == RR_Connected && info->crtc != None) { - // get monitor data and update info - PalMonitor* monitor = TO_PAL_HANDLE(PalMonitor, output); - MonitorData* data = nullptr; - data = getFreeMonitorData(); - if (!data) { - return; - } - - data->monitor = monitor; - - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); - // clang-format on - - // get DPI - float raw = crtc->width / 1920.0f; - float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; - float closest = steps[0]; - float minDiff = fabsf(raw - steps[0]); - - for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { - float diff = fabsf(raw - steps[i]); - if (diff < minDiff) { - minDiff = diff; - closest = steps[i]; - } - } - - data->dpi = (Uint32)(closest * 96.0f); - data->w = crtc->width; - data->h = crtc->height; - data->x = crtc->x; - data->y = crtc->y; - - s_X11.freeCrtcInfo(crtc); - s_X11.monitorCount++; - } - - s_X11.freeOutputInfo(info); - } - - s_X11.freeScreenResources(resources); -} - -static int xGetWindowMonitorDPI(WindowData* data) -{ - int winX = data->x + data->w / 2; - int winY = data->y + data->w / 2; - // get the DPI from our cached monitor - for (int i = 0; i < s_Video.maxMonitorData; i++) { - if (!s_Video.monitorData->used) { - continue; - } - - // we found a monitor, check the monitor bounds with the window - MonitorData* info = &s_Video.monitorData[i]; - if (winX >= info->x && winX < info->x + info->w && winY >= info->y && - winY < info->y + info->h) { - // found monitor - return info->dpi; - } - } -} - -static void xSendWMEvent( - Window window, - Atom type, - long a, - long b, - long c, - long d, - bool add) -{ - XEvent e = {0}; - e.xclient.type = ClientMessage; - e.xclient.send_event = True; - e.xclient.window = window; - e.xclient.message_type = type; - e.xclient.format = 32; - if (add) { - e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD - } else { - e.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE - } - - e.xclient.data.l[1] = a; - e.xclient.data.l[2] = b; - e.xclient.data.l[3] = c; - e.xclient.data.l[4] = d; - - s_X11.sendEvent( - s_X11.display, - s_X11.root, - False, - SubstructureNotifyMask | SubstructureRedirectMask, - &e); -} - -static void xCreateKeycodeTable() -{ - // Tis is for only printable and text input keys - - // Letters - s_Keyboard.keycodes[XK_a] = PAL_KEYCODE_A; - s_Keyboard.keycodes[XK_b] = PAL_KEYCODE_B; - s_Keyboard.keycodes[XK_c] = PAL_KEYCODE_C; - s_Keyboard.keycodes[XK_d] = PAL_KEYCODE_D; - s_Keyboard.keycodes[XK_e] = PAL_KEYCODE_E; - s_Keyboard.keycodes[XK_f] = PAL_KEYCODE_F; - s_Keyboard.keycodes[XK_g] = PAL_KEYCODE_G; - s_Keyboard.keycodes[XK_h] = PAL_KEYCODE_H; - s_Keyboard.keycodes[XK_i] = PAL_KEYCODE_I; - s_Keyboard.keycodes[XK_j] = PAL_KEYCODE_J; - s_Keyboard.keycodes[XK_k] = PAL_KEYCODE_K; - s_Keyboard.keycodes[XK_l] = PAL_KEYCODE_L; - s_Keyboard.keycodes[XK_m] = PAL_KEYCODE_M; - s_Keyboard.keycodes[XK_n] = PAL_KEYCODE_N; - s_Keyboard.keycodes[XK_o] = PAL_KEYCODE_O; - s_Keyboard.keycodes[XK_p] = PAL_KEYCODE_P; - s_Keyboard.keycodes[XK_q] = PAL_KEYCODE_Q; - s_Keyboard.keycodes[XK_r] = PAL_KEYCODE_R; - s_Keyboard.keycodes[XK_s] = PAL_KEYCODE_S; - s_Keyboard.keycodes[XK_t] = PAL_KEYCODE_T; - s_Keyboard.keycodes[XK_u] = PAL_KEYCODE_U; - s_Keyboard.keycodes[XK_v] = PAL_KEYCODE_V; - s_Keyboard.keycodes[XK_w] = PAL_KEYCODE_W; - s_Keyboard.keycodes[XK_x] = PAL_KEYCODE_X; - s_Keyboard.keycodes[XK_y] = PAL_KEYCODE_Y; - s_Keyboard.keycodes[XK_z] = PAL_KEYCODE_Z; - - // Control - s_Keyboard.keycodes[XK_space] = PAL_KEYCODE_SPACE; - - // Misc - s_Keyboard.keycodes[XK_apostrophe] = PAL_KEYCODE_APOSTROPHE; - s_Keyboard.keycodes[XK_backslash] = PAL_KEYCODE_BACKSLASH; - s_Keyboard.keycodes[XK_comma] = PAL_KEYCODE_COMMA; - s_Keyboard.keycodes[XK_equal] = PAL_KEYCODE_EQUAL; - s_Keyboard.keycodes[XK_grave] = PAL_KEYCODE_GRAVEACCENT; - s_Keyboard.keycodes[XK_minus] = PAL_KEYCODE_SUBTRACT; - s_Keyboard.keycodes[XK_period] = PAL_KEYCODE_PERIOD; - s_Keyboard.keycodes[XK_semicolon] = PAL_KEYCODE_SEMICOLON; - s_Keyboard.keycodes[XK_slash] = PAL_KEYCODE_SLASH; - s_Keyboard.keycodes[XK_bracketleft] = PAL_KEYCODE_LBRACKET; - s_Keyboard.keycodes[XK_bracketright] = PAL_KEYCODE_RBRACKET; -} - -static PalResult xInitVideo() -{ - // load X11 library - s_X11.handle = dlopen("libX11.so", RTLD_LAZY); - if (!s_X11.handle) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // libXCursor is needed - s_X11.libCursor = dlopen("libXcursor.so", RTLD_LAZY); - if (!s_X11.libCursor) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // Xrandr is needed - s_X11.xrandr = dlopen("libXrandr.so.2", RTLD_LAZY); - if (!s_X11.xrandr) { - s_X11.xrandr = dlopen("libXrandr.so", RTLD_LAZY); - } - - // clang-format off - - // load procs - s_X11.openDisplay = (XOpenDisplayFn)dlsym( - s_X11.handle, - "XOpenDisplay"); - - s_X11.closeDisplay = (XCloseDisplayFn)dlsym( - s_X11.handle, - "XCloseDisplay"); - - s_X11.getWindowAttributes = (XGetWindowAttributesFn)dlsym( - s_X11.handle, - "XGetWindowAttributes"); - - s_X11.setCrtcConfig = (XRRSetCrtcConfigFn)dlsym( - s_X11.handle, - "XRRSetCrtcConfig"); - - s_X11.getWindowProperty = (XGetWindowPropertyFn)dlsym( - s_X11.handle, - "XGetWindowProperty"); - - s_X11.internAtom = (XInternAtomFn)dlsym( - s_X11.handle, - "XInternAtom"); - - s_X11.getSelectionOwner = (XGetSelectionOwnerFn)dlsym( - s_X11.handle, - "XGetSelectionOwner"); - - s_X11.freeColormap = (XFreeColormapFn)dlsym( - s_X11.handle, - "XFreeColormap"); - - s_X11.storeName = (XStoreNameFn)dlsym( - s_X11.handle, - "XStoreName"); - - s_X11.changeProperty = (XChangePropertyFn)dlsym( - s_X11.handle, - "XChangeProperty"); - - s_X11.flush = (XFlushFn)dlsym( - s_X11.handle, - "XFlush"); - - s_X11.createColormap = (XCreateColormapFn)dlsym( - s_X11.handle, - "XCreateColormap"); - - s_X11.mapWindow = (XMapWindowFn)dlsym( - s_X11.handle, - "XMapWindow"); - - s_X11.unmapWindow = (XUnmapWindowFn)dlsym( - s_X11.handle, - "XUnmapWindow"); - - s_X11.createWindow = (XCreateWindowFn)dlsym( - s_X11.handle, - "XCreateWindow"); - - s_X11.destroyWindow = (XDestroyWindowFn)dlsym( - s_X11.handle, - "XDestroyWindow"); - - s_X11.matchVisualInfo = (XMatchVisualInfoFn)dlsym( - s_X11.handle, - "XMatchVisualInfo"); - - s_X11.pending = (XPendingFn)dlsym( - s_X11.handle, - "XPending"); - - s_X11.setWMProtocols = (XSetWMProtocolsFn)dlsym( - s_X11.handle, - "XSetWMProtocols"); - - s_X11.nextEvent = (XNextEventFn)dlsym( - s_X11.handle, - "XNextEvent"); - - s_X11.setWMNormalHints = (XSetWMNormalHintsFn)dlsym( - s_X11.handle, - "XSetWMNormalHints"); - - s_X11.getWMNormalHints = (XGetWMNormalHintsFn)dlsym( - s_X11.handle, - "XGetWMNormalHints"); - - s_X11.sendEvent = (XSendEventFn)dlsym( - s_X11.handle, - "XSendEvent"); - - s_X11.moveWindow = (XMoveWindowFn)dlsym( - s_X11.handle, - "XMoveWindow"); - - s_X11.resizeWindow = (XResizeWindowFn)dlsym( - s_X11.handle, - "XResizeWindow"); - - s_X11.iconifyWindow = (XIconifyWindowFn)dlsym( - s_X11.handle, - "XIconifyWindow"); - - s_X11.setErrorHandler = (XSetErrorHandlerFn)dlsym( - s_X11.handle, - "XSetErrorHandler"); - - s_X11.sync = (XSyncFn)dlsym( - s_X11.handle, - "XSync"); - - s_X11.saveContext = (XSaveContextFn)dlsym( - s_X11.handle, - "XSaveContext"); - - s_X11.findContext = (XFindContextFn)dlsym( - s_X11.handle, - "XFindContext"); - - s_X11.uniqueContext = (XrmUniqueQuarkFn)dlsym( - s_X11.handle, - "XrmUniqueQuark"); - - // load Xrandr functions - s_X11.getScreenResources = (XRRGetScreenResourcesFn)dlsym( - s_X11.xrandr, - "XRRGetScreenResources"); - - s_X11.getOutputPrimary = (XRRGetOutputPrimaryFn)dlsym( - s_X11.xrandr, - "XRRGetOutputPrimary"); - - s_X11.getOutputInfo = (XRRGetOutputInfoFn)dlsym( - s_X11.xrandr, - "XRRGetOutputInfo"); - - s_X11.getCrtcInfo = (XRRGetCrtcInfoFn)dlsym( - s_X11.xrandr, - "XRRGetCrtcInfo"); - - s_X11.freeScreenResources = (XRRFreeScreenResourcesFn)dlsym( - s_X11.xrandr, - "XRRFreeScreenResources"); - - s_X11.freeOutputInfo = (XRRFreeOutputInfoFn)dlsym( - s_X11.xrandr, - "XRRFreeScreenResources"); - - s_X11.freeCrtcInfo = (XRRFreeCrtcInfoFn)dlsym( - s_X11.xrandr, - "XRRFreeCrtcInfo"); - - s_X11.selectRRInput = (XRRSelectInputFn)dlsym( - s_X11.xrandr, - "XRRSelectInput"); - - s_X11.queryRRExtension = (XRRQueryExtensionFn)dlsym( - s_X11.xrandr, - "XRRQueryExtension"); - - s_X11.allocClassHint = (XAllocClassHintFn)dlsym( - s_X11.handle, - "XAllocClassHint"); - - s_X11.setClassHint = (XSetClassHintFn)dlsym( - s_X11.handle, - "XSetClassHint"); - - s_X11.free = (XFreeFn)dlsym( - s_X11.handle, - "XFree"); - - s_X11.getVisualInfo = (XGetVisualInfoFn)dlsym( - s_X11.handle, - "XGetVisualInfo"); - - s_X11.createFontCursor = (XCreateFontCursorFn)dlsym( - s_X11.handle, - "XCreateFontCursor"); - - s_X11.freePixmap = (XFreePixmapFn)dlsym( - s_X11.handle, - "XFreePixmap"); - - s_X11.setWMHints = (XSetWMHintsFn)dlsym( - s_X11.handle, - "XSetWMHints"); - - s_X11.grabPointer = (XGrabPointerFn)dlsym( - s_X11.handle, - "XGrabPointer"); - - s_X11.createPixmapCursor = (XCreatePixmapCursorFn)dlsym( - s_X11.handle, - "XCreatePixmapCursor"); - - s_X11.warpPointer = (XWarpPointerFn)dlsym( - s_X11.handle, - "XWarpPointer"); - - s_X11.getWMName = (XGetWMNameFn)dlsym( - s_X11.handle, - "XGetWMName"); - - s_X11.queryPointer = (XQueryPointerFn)dlsym( - s_X11.handle, - "XQueryPointer"); - - s_X11.ungrabPointer = (XUngrabPointerFn)dlsym( - s_X11.handle, - "XUngrabPointer"); - - s_X11.allocWMHints = (XAllocWMHintsFn)dlsym( - s_X11.handle, - "XAllocWMHints"); - - s_X11.mapRaised = (XMapRaisedFn)dlsym( - s_X11.handle, - "XMapRaised"); - - s_X11.undefineCursor = (XUndefineCursorFn)dlsym( - s_X11.handle, - "XUndefineCursor"); - - s_X11.defineCursor = (XDefineCursorFn)dlsym( - s_X11.handle, - "XDefineCursor"); - - s_X11.freeCursor = (XFreeCursorFn)dlsym( - s_X11.handle, - "XFreeCursor"); - - s_X11.getWMHints = (XGetWMHintsFn)dlsym( - s_X11.handle, - "XGetWMHints"); - - s_X11.createPixmap = (XCreatePixmapFn)dlsym( - s_X11.handle, - "XCreatePixmap"); - - s_X11.setInputFocus = (XSetInputFocusFn)dlsym( - s_X11.handle, - "XSetInputFocus"); - - s_X11.getInputFocus = (XGetInputFocusFn)dlsym( - s_X11.handle, - "XGetInputFocus"); - - s_X11.selectInput = (XSelectInputFn)dlsym( - s_X11.handle, - "XSelectInput"); - - // libXcursor - s_X11.cursorImageLoadCursor = (XcursorImageLoadCursorFn)dlsym( - s_X11.libCursor, - "XcursorImageLoadCursor"); - - s_X11.cursorImageCreate = (XcursorImageCreateFn)dlsym( - s_X11.libCursor, - "XcursorImageCreate"); - - s_X11.cursorImageDestroy = (XcursorImageDestroyFn)dlsym( - s_X11.libCursor, - "XcursorImageDestroy"); - - s_X11.lookupKeysym = (XLookupKeysymFn)dlsym( - s_X11.libCursor, - "XLookupKeysym"); - - s_X11.setDetectableAutoRepeat = (XkbSetDetectableAutoRepeatFn)dlsym( - s_X11.handle, - "XkbSetDetectableAutoRepeat"); - - s_X11.setLocaleModifiers = (XSetLocaleModifiersFn)dlsym( - s_X11.handle, - "XSetLocaleModifiers"); - - s_X11.openIM = (XOpenIMFn)dlsym( - s_X11.handle, - "XOpenIM"); - - s_X11.closeIM = (XCloseIMFn)dlsym( - s_X11.handle, - "XCloseIM"); - - s_X11.createIC = (XCreateICFn)dlsym( - s_X11.handle, - "XCreateIC"); - - s_X11.destroyIC = (XDestroyICFn)dlsym( - s_X11.handle, - "XDestroyIC"); - - s_X11.utf8LookupString = (Xutf8LookupStringFn)dlsym( - s_X11.handle, - "Xutf8LookupString"); - - // X11 server - if (s_Video.platformInstance) { - s_X11.display = (Display*)s_Video.platformInstance; - - } else { - s_X11.display = s_X11.openDisplay(nullptr); - s_Video.platformInstance = nullptr; - } - - if (!s_X11.display) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_X11.root = DefaultRootWindow(s_X11.display); - s_X11.screen = DefaultScreen(s_X11.display); - - xCheckFeatures(); - - // subscribe for monitor events - s_X11.selectRRInput( - s_X11.display, - s_X11.root, - RRScreenChangeNotifyMask | RRNotify); - - int eventBase, errorBase = 0; - s_X11.queryRRExtension(s_X11.display, &eventBase, &errorBase); - s_X11.rrEventBase = eventBase; - s_X11.skipScreenEvent = true; - - s_X11.dataID = (XContext)s_X11.uniqueContext(); - resetMonitorData(); - - s_X11.monitorCount = 0; - xCacheMonitors(); - - // since X11 supports both EGL and GLX - // we try to load them and resolve the needed functions - - // we load GLX - s_X11.glxHandle = dlopen("libGL.so.1", RTLD_LAZY); - if (s_X11.glxHandle) { - - GLXGetProcAddressFn load = nullptr; - load = (GLXGetProcAddressFn)dlsym( - s_X11.glxHandle, - "glXGetProcAddress"); - - s_X11.glxGetFBConfigs = (GLXGetFBConfigsFn)load( - "glXGetFBConfigs"); - - s_X11.glxGetFBConfigAttrib = (GLXGetFBConfigAttribFn)load( - "glXGetFBConfigAttrib"); - - s_X11.glxGetVisualFromFBConfig = (GLXGetVisualFromFBConfigFn)load( - "glXGetVisualFromFBConfig"); - } - - xCreateKeycodeTable(); - - // disable auto key repeats - int supported; - s_X11.setDetectableAutoRepeat(s_X11.display, True, &supported); - if (!supported) { - // FIXME: fallback to manual key repeat detection - } - - // create an input method - s_X11.setLocaleModifiers(""); - s_X11.im = s_X11.openIM(s_X11.display, nullptr, nullptr, nullptr); - if (s_X11.im == None) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_Video.display = (void*)s_X11.display; - return PAL_RESULT_SUCCESS; -} - -static void xShutdownVideo() -{ - s_X11.closeIM(s_X11.im); - if (!s_Video.platformInstance) { - // opened by PAL - s_X11.closeDisplay(s_X11.display); - } - - dlclose(s_X11.handle); - dlclose(s_X11.xrandr); - dlclose(s_X11.libCursor); - - if (s_X11.glxHandle) { - dlclose(s_X11.glxHandle); - } - memset(&s_X11, 0, sizeof(X11)); - memset(&s_X11Atoms, 0, sizeof(X11Atoms)); -} - -PalResult xSetFBConfig( - const int index, - PalFBConfigBackend backend) -{ - if (backend == PAL_CONFIG_BACKEND_GLX) { - return glxBackend(index); - - } else if (backend == PAL_CONFIG_BACKEND_EGL || - backend == PAL_CONFIG_BACKEND_PAL_OPENGL) { - return eglXBackend(index); - - } else { - return PAL_RESULT_INVALID_FBCONFIG_BACKEND; - } -} - -static void xUpdateVideo() -{ - XEvent event; - PalDispatchMode mode = PAL_DISPATCH_NONE; - while (s_X11.pending(s_X11.display)) { - s_X11.nextEvent(s_X11.display, &event); - - Window xWin = event.xany.window; - PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); - WindowData* data = nullptr; - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); - - if (event.type == s_X11.rrEventBase + RRScreenChangeNotify) { - event.type = RANDR_SCREEN_CHANGE_EVENT; // for switch flow - } - - switch (event.type) { - case ClientMessage: { - // check for window close - Atom windowClose = event.xclient.data.l[0]; - if (windowClose == s_X11Atoms.WM_DELETE_WINDOW) { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_CLOSE; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - break; - } - - case ConfigureNotify: { - // window resize or move - - // skip the first configure event - if (data->skipConfigure) { - data->skipConfigure = false; - data->w = event.xconfigure.width; - data->h = event.xconfigure.height; - data->x = event.xconfigure.x; - data->y = event.xconfigure.y; - break; - } - - // real configure event - if (s_Video.eventDriver) { - // check if its a resize event - if (data->w != event.xconfigure.width || - data->h != event.xconfigure.height) { - data->w = event.xconfigure.width; - data->h = event.xconfigure.height; - - // push a resize event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_SIZE; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(data->w, data->h); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - - // attach windows sometimes bypass - // skipConfgure an still send an initial move event - if (data->isAttached) { - if (data->skipIfAttached) { - data->skipIfAttached = false; - break; - } - } - - // check if its a move event - if (data->x != event.xconfigure.x || - data->y != event.xconfigure.y) { - data->x = event.xconfigure.x; - data->y = event.xconfigure.y; - - // push a move event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_MOVE; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(data->x, data->y); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - - /** a window has to be moved - before its can change monitors - we get the monitor the moved - window is on and check if the dpi is different - from the one it was created on */ - int monitorDPI = xGetWindowMonitorDPI(data); - if (monitorDPI != data->dpi) { - // window is on a different monitor - data->dpi = monitorDPI; - - // push a DPI event - type = PAL_EVENT_MONITOR_DPI_CHANGED; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = monitorDPI; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - } - break; - } - - case FocusIn: { - // window has gained focus - if (s_Video.eventDriver) { - int mode = event.xfocus.mode; - if (mode == NotifyGrab || mode == NotifyUngrab) { - // ignore dragging and popup focus events - break; - } - - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_FOCUS; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = true; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - break; - } - - case FocusOut: { - // window has lost focus - if (s_Video.eventDriver) { - int mode = event.xfocus.mode; - if (mode == NotifyGrab || mode == NotifyUngrab) { - // ignore dragging and popup focus events - break; - } - - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_FOCUS; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = false; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - break; - } - - case PropertyNotify: { - // check window state (maximize, minimize) - if (event.xproperty.atom == s_X11Atoms._NET_WM_STATE) { - PalWindowState state; - state = xQueryWindowState(event.xproperty.window); - if (state != data->state) { - data->state = state; - - // skip the first state event - if (data->skipState) { - data->skipState = false; - break; - } - - // push event - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_STATE; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = data->state; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - break; - } - - case RANDR_SCREEN_CHANGE_EVENT: { - // skip the first event - if (s_X11.skipScreenEvent) { - s_X11.skipScreenEvent = false; - break; - } - - // store old monitor count - int oldCount = s_X11.monitorCount; - s_X11.monitorCount = 0; - xCacheMonitors(); - - if (oldCount != s_X11.monitorCount) { - // a monitor has been added or removed - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MONITOR_LIST_CHANGED; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - } - break; - } - - case MotionNotify: { - // mouse moved - const int x = event.xmotion.x; - const int y = event.xmotion.y; - const int dx = x - s_Mouse.lastX; - const int dy = y - s_Mouse.lastY; - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MOUSE_MOVE; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(x, y); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - - // push a mouse delta event - type = PAL_EVENT_MOUSE_DELTA; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(dx, dy); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - - s_Mouse.lastX = x; - s_Mouse.lastY = y; - s_Mouse.dx = dx; - s_Mouse.dy = dy; - break; - } - - case ButtonPress: - case ButtonRelease: { - int xButton = event.xbutton.button; - bool pressed = (event.xbutton.type == ButtonPress); - PalMouseButton button = 0; - PalEventType type; - - if (xButton == 1) { - button = PAL_MOUSE_BUTTON_LEFT; - } else if (xButton == 3) { - button = PAL_MOUSE_BUTTON_RIGHT; - } else if (xButton == 2) { - button = PAL_MOUSE_BUTTON_MIDDLE; - } - - s_Mouse.state[button] = pressed; - if (s_Video.eventDriver && button != 0) { - PalEventDriver* driver = s_Video.eventDriver; - if (pressed) { - type = PAL_EVENT_MOUSE_BUTTONDOWN; - } else { - type = PAL_EVENT_MOUSE_BUTTONUP; - } - - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(button, NULL_BUTTON_SERIAL); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - - int scrollX = 0; - int scrollY = 0; - if (xButton == 4) { - // scroll up - scrollY = 1; - } else if (xButton == 5) { - // scroll down - scrollY = -1; - } else if (xButton == 6) { - // scroll left - scrollX = -1; - } else if (xButton == 7) { - // scroll right - scrollX = 1; - } - - s_Mouse.WheelX = scrollX; - s_Mouse.WheelY = scrollY; - if (s_Video.eventDriver && (scrollX || scrollY)) { - PalEventDriver* driver = s_Video.eventDriver; - // clang-format off - mode = palGetEventDispatchMode(driver, PAL_EVENT_MOUSE_WHEEL); - // clang-format on - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_MOUSE_WHEEL; - event.data = palPackInt32(scrollX, scrollY); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - break; - } - - case KeyPress: - case KeyRelease: { - int xScancode = event.xkey.keycode; - bool pressed = (event.xbutton.type == KeyPress); - PalScancode scancode = PAL_SCANCODE_UNKNOWN; - PalKeycode keycode = PAL_KEYCODE_UNKNOWN; - PalEventType type; - KeySym keySym = s_X11.lookupKeysym(&event.xkey, 0); - - // special handling for Pause/break with Home - if (xScancode == 110) { - if (keySym == XK_Pause) { - scancode = PAL_SCANCODE_PAUSE; - } else { - scancode = PAL_SCANCODE_HOME; - } - - } else { - int index = xScancode - 8; - scancode = s_Keyboard.scancodes[index]; - } - - // printable and text input keys are from the range - // 32 (PAL_KEYCODE_SPACE) and 122 (PAL_KEYCODE_Z) - // The rest are almost the same as their scancode - // Maybe there will be a layout that makes this wrong - // but for now this works - if (keySym >= XK_space && keySym <= XK_z) { - // a printable or input key - keycode = s_Keyboard.keycodes[keySym]; - - } else { - // Since PalKeycode and PalScancode have the same integers - // we can make a direct cast without a table - // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) - keycode = (PalKeycode)(Uint32)scancode; - } - - // If we got a keySym but its not mapped into our keycode array - // we do a direct cast as well - if (keycode == PAL_KEYCODE_UNKNOWN) { - keycode = (PalKeycode)(Uint32)scancode; - } - - // update our keyboard and mouse state to handle key repeat - bool repeat = s_Keyboard.keycodeState[keycode]; - s_Keyboard.scancodeState[scancode] = pressed; - s_Keyboard.keycodeState[keycode] = pressed; - - if (pressed) { - if (repeat) { - type = PAL_EVENT_KEYREPEAT; - } else { - type = PAL_EVENT_KEYDOWN; - } - } else { - type = PAL_EVENT_KEYUP; - } - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - - // check for char event if enabled - type = PAL_EVENT_KEYCHAR; - mode = palGetEventDispatchMode(driver, type); - if (mode == PAL_DISPATCH_NONE) { - break; - } - - int status; - char buffer[32]; - KeySym keySym; - int len = s_X11.utf8LookupString( - data->ic, - &event.xkey, - buffer, - sizeof(buffer), - &keySym, - &status); - - Uint32 codepoint = 0; - if (status == XLookupChars || status == XLookupBoth) { - // decode to Unicode codepoint - unsigned char ch = buffer[0]; - if (ch < 0x80) { - // 1 byte (A-Z) - codepoint = ch; - - } else if ((ch >> 5) == 0x6 && len >= 2) { - // 2 byte - codepoint = ((ch & 0x1F) << 6) | buffer[1] & 0x3F; - - } else if ((ch >> 4) == 0xE && len >= 3) { - // 3 byte - // clang-format off - codepoint = ((ch & 0x0F) << 12) | - ((buffer[1] & 0x3F) << 6) | - (buffer[2] & 0x3F); - // clang-format on - - } else if ((ch >> 3) == 0x1E && len >= 4) { - // 4 byte - // clang-format off - codepoint = ((ch & 0x07) << 18) | - ((buffer[1] & 0x3F) << 12) | - ((buffer[2] & 0x3F) << 6) | - (buffer[3] & 0x3F); - // clang-format on - } - - PalEvent event = {0}; - event.type = type; - event.data = codepoint; - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - } - } - break; - } - } - } - - s_X11.flush(s_X11.display); -} - -static PalResult xEnumerateMonitors( - Int32* count, - PalMonitor** outMonitors) -{ - int _count = 0; - int maxCount = outMonitors ? *count : 0; - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - for (int i = 0; i < resources->noutput; ++i) { - RROutput output = resources->outputs[i]; - // clang-format off - XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); - // clang-format on - - if (outputInfo->connection == RR_Connected && - outputInfo->crtc != None) { - // a monitor - if (outMonitors) { - if (_count < maxCount) { - outMonitors[_count] = TO_PAL_HANDLE(PalMonitor, output); - } - } - _count++; - } - } - - if (!outMonitors) { - *count = _count; - } - return PAL_RESULT_SUCCESS; -} - -static PalResult xGetPrimaryMonitor(PalMonitor** outMonitor) -{ - RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); - if (primary) { - *outMonitor = TO_PAL_HANDLE(PalMonitor, primary); - return PAL_RESULT_SUCCESS; - } - - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; -} - -static PalResult xGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info) -{ - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - strcpy(info->name, outputInfo->name); - - // check if its primary monitor - RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); - - if (monitor == TO_PAL_HANDLE(PalMonitor, primary)) { - info->primary = true; - } else { - info->primary = false; - } - - // get monitor pos and size - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on - - info->x = crtc->x; - info->y = crtc->y; - info->width = crtc->width; - info->height = crtc->height; - - // get refresh rate - double rate = 0; - for (int i = 0; i < resources->nmode; ++i) { - // check for our monitor - if (resources->modes[i].id == crtc->mode) { - XRRModeInfo* mode = &resources->modes[i]; - double tmp = (double)mode->hTotal * (double)mode->vTotal; - rate = (double)mode->dotClock / tmp; - info->refreshRate = rate + 0.5; - break; - } - } - - // orientation - switch (crtc->rotation) { - case RR_Rotate_0: { - info->orientation = PAL_ORIENTATION_LANDSCAPE; - break; - } - - case RR_Rotate_90: { - info->orientation = PAL_ORIENTATION_PORTRAIT; - break; - } - - case RR_Rotate_180: { - info->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - } - - case RR_Rotate_270: { - info->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; - break; - } - - default: { - info->orientation = PAL_ORIENTATION_LANDSCAPE; - } - } - - // get dpi - float raw = crtc->width / 1920.0f; - float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; - float closest = steps[0]; - float minDiff = fabsf(raw - steps[0]); - - for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { - float diff = fabsf(raw - steps[i]); - if (diff < minDiff) { - minDiff = diff; - closest = steps[i]; - } - } - - info->dpi = (Uint32)(closest * 96.0f); - - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - - return PAL_RESULT_SUCCESS; -} - -static PalResult xEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes) -{ - Int32 modeCount = 0; - int maxModeCount = modes ? *count : 0; - - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - // get supported display modes - for (int i = 0; i < outputInfo->nmode; ++i) { - for (int j = 0; j < resources->nmode; ++j) { - // get the display mode and check if its for our monitor - XRRModeInfo* info = &resources->modes[j]; - if (info->id == outputInfo->modes[i]) { - // check if user supplied a PalMonitorMode array - if (modes) { - if (modeCount < maxModeCount) { - PalMonitorMode* mode = &modes[modeCount]; - mode->width = info->width; - mode->height = info->height; - mode->bpp = s_X11.bpp; - - // clang-format off - double tmp = (double)info->hTotal * (double)info->vTotal; - // clang-format on - - double rate = (double)info->dotClock / tmp; - mode->refreshRate = rate + 0.5; - } - } - modeCount++; - } - } - } - - if (!modes) { - *count = modeCount; - } - - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - - return PAL_RESULT_SUCCESS; -} - -static PalResult xGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - // get the current display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on - - // find the display mode - XRRModeInfo* info = nullptr; - for (int i = 0; i < resources->nmode; ++i) { - if (resources->modes[i].id == crtc->mode) { - // found - info = &resources->modes[i]; - break; - } - } - - if (mode) { - mode->width = info->width; - mode->height = info->height; - mode->bpp = s_X11.bpp; - - double tmp = (double)info->hTotal * (double)info->vTotal; - double rate = (double)info->dotClock / tmp; - mode->refreshRate = rate + 0.5; - } - - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - - return PAL_RESULT_SUCCESS; -} - -static PalResult xSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - // find the monitor display mode - RRMode displayMode = xFindMode(resources, mode); - if (displayMode == None) { - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR_MODE; - } - - // apply the display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on - - RROutput output = FROM_PAL_HANDLE(RROutput, monitor); - int ret = s_X11.setCrtcConfig( - s_X11.display, - resources, - outputInfo->crtc, - CurrentTime, - crtc->x, - crtc->y, - displayMode, - crtc->rotation, - &output, - 1); - - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - - if (ret != Success) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - return PAL_RESULT_SUCCESS; -} - -static PalResult xValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -static PalResult xSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation) -{ - // clang-format off - XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - // clang-format on - - // get the monitor info - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - if (!outputInfo) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - if (outputInfo->connection != RR_Connected) { - // invalid monitor - s_X11.freeScreenResources(resources); - return PAL_RESULT_INVALID_MONITOR; - } - - // get the current display mode - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); - // clang-format on - - // check if the new orientation is supported - Rotation rotation = 0; - switch (orientation) { - case PAL_ORIENTATION_LANDSCAPE: { - rotation = RR_Rotate_0; - break; - } - - case PAL_ORIENTATION_PORTRAIT: { - rotation = RR_Rotate_90; - break; - } - - case PAL_ORIENTATION_LANDSCAPE_FLIPPED: { - rotation = RR_Rotate_180; - break; - } - - case PAL_ORIENTATION_PORTRAIT_FLIPPED: { - rotation = RR_Rotate_270; - break; - } - - default: { - return PAL_RESULT_INVALID_ORIENTATION; - } - } - - if (!(crtc->rotations & rotation)) { - return PAL_RESULT_INVALID_ORIENTATION; - } - - RROutput output = FROM_PAL_HANDLE(RROutput, monitor); - int ret = s_X11.setCrtcConfig( - s_X11.display, - resources, - outputInfo->crtc, - CurrentTime, - crtc->x, - crtc->y, - crtc->mode, - rotation, - &output, - 1); - - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - s_X11.freeScreenResources(resources); - - if (ret != Success) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - return PAL_RESULT_SUCCESS; -} - -static PalResult xCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow) -{ - Window window = None; - PalMonitor* monitor = nullptr; - PalMonitorInfo monitorInfo; - - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - Visual* visual = nullptr; - int depth = 0; - Colormap colormap = None; - unsigned long bgPixel = 0; - unsigned long borderPixel = 0; - - if (s_X11.visualInfo) { - visual = s_X11.visualInfo->visual; - depth = s_X11.visualInfo->depth; - bgPixel = 0; - borderPixel = 0; - - // clang-format off - - colormap = s_X11.createColormap( - s_X11.display, - s_X11.root, - visual, - AllocNone); - // clang-format on - - if (!colormap) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - data->colormap = colormap; - - } else { - // use a default visual - visual = DefaultVisual(s_X11.display, s_X11.screen); - depth = DefaultDepth(s_X11.display, s_X11.screen); - bgPixel = WhitePixel(s_X11.display, s_X11.screen); - borderPixel = BlackPixel(s_X11.display, s_X11.screen); - colormap = DefaultColormap(s_X11.display, s_X11.screen); - data->colormap = None; - } - - // get monitor - int monitorX = 0; - int monitorY = 0; - Uint32 monitorW = 0; - Uint32 monitorH = 0; - int dpi = 0; - if (info->monitor) { - monitor = info->monitor; - - } else { - // get primary monitor - xGetPrimaryMonitor(&monitor); - } - - if (monitor) { - // get monitor info - PalResult result = palGetMonitorInfo(monitor, &monitorInfo); - if (result != PAL_RESULT_SUCCESS) { - return result; - } - - monitorX = monitorInfo.x; - monitorY = monitorInfo.y; - monitorW = monitorInfo.width; - monitorH = monitorInfo.height; - dpi = monitorInfo.dpi; - - } else { - // primary monitor is not set - XRRScreenResources* resources = nullptr; - resources = s_X11.getScreenResources(s_X11.display, s_X11.root); - for (int i = 0; i < resources->noutput; ++i) { - RROutput output = resources->outputs[i]; - XRROutputInfo* outputInfo = s_X11.getOutputInfo( - s_X11.display, - resources, - FROM_PAL_HANDLE(RROutput, monitor)); - - // check if its a monitor - if (outputInfo->connection != RR_Connected || - outputInfo->crtc == None) { - s_X11.freeOutputInfo(outputInfo); - continue; - } - - // clang-format off - XRRCrtcInfo* crtc = s_X11.getCrtcInfo( - s_X11.display, - resources, - outputInfo->crtc); - // clang-format on - - monitorX = crtc->x; - monitorY = crtc->y; - monitorW = crtc->width; - monitorH = crtc->height; - - // get DPI - float raw = crtc->width / 1920.0f; - float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; - float closest = steps[0]; - float minDiff = fabsf(raw - steps[0]); - - for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { - float diff = fabsf(raw - steps[i]); - if (diff < minDiff) { - minDiff = diff; - closest = steps[i]; - } - } - - dpi = (Uint32)(closest * 96.0f); - - s_X11.freeCrtcInfo(crtc); - s_X11.freeOutputInfo(outputInfo); - break; - } - s_X11.freeScreenResources(resources); - } - - Int32 x, y = 0; - // the position and size must be scaled with the dpi before this call - if (info->center) { - x = monitorX + (monitorW - info->width) / 2; - y = monitorY + (monitorH - info->height) / 2; - - } else { - // we set 100 for each axix - x = monitorX + 100; - y = monitorY + 100; - } - - // check and set transparency - if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - // we dont need to set any flag - } - - long mask = StructureNotifyMask | KeyPressMask; - mask |= KeyReleaseMask; - mask |= ButtonPressMask; - mask |= ButtonReleaseMask; - mask |= PointerMotionMask; - mask |= FocusChangeMask; - mask |= PropertyChangeMask; - - XSetWindowAttributes attrs = {0}; - attrs.colormap = colormap; - attrs.event_mask = mask; - attrs.background_pixel = bgPixel; - attrs.border_pixel = borderPixel; - attrs.override_redirect = False; - - // create window - window = s_X11.createWindow( - s_X11.display, - s_X11.root, - x, - y, - info->width, - info->height, - 0, // border width - depth, - InputOutput, // class - visual, - CWEventMask | CWColormap | CWBorderPixel | CWBackPixel, - &attrs); - - if (window == None) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // set pid property - pid_t pid = getpid(); - s_X11.changeProperty( - s_X11.display, - window, - s_X11Atoms._NET_WM_PID, - XA_CARDINAL, - 32, - PropModeReplace, - (unsigned char*)&pid, - 1); - - // set class property - XClassHint* hints = s_X11.allocClassHint(); - if (hints) { - const char* resName = getenv("RESOURCE_NAME"); - const char* resClass = getenv("RESOURCE_CLASS"); - - if (!resName || strlen(resName) == 0) { - resName = info->title; - } - - if (!resClass || strlen(resClass) == 0) { - resClass = s_Video.className; - } - - hints->res_name = (char*)resName; - hints->res_class = (char*)resClass; - s_X11.setClassHint(s_X11.display, window, hints); - s_X11.free(hints); - } - - if (s_X11Atoms.unicodeTitle && info->title) { - s_X11.changeProperty( - s_X11.display, - window, - s_X11Atoms._NET_WM_NAME, - s_X11Atoms.UTF8_STRING, - 8, // unsigned char - PropModeReplace, - info->title, - strlen(info->title)); - - } else { - if (info->title) { - s_X11.storeName(s_X11.display, window, info->title); - } - } - - // borderless - if (info->style & PAL_WINDOW_STYLE_BORDERLESS) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW)) { - s_X11.destroyWindow(s_X11.display, window); - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - s_X11.changeProperty( - s_X11.display, - window, - s_X11Atoms._NET_WM_WINDOW_TYPE, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH, - 1); - } - - // tool window - if (info->style & PAL_WINDOW_STYLE_TOOL) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_TOOL_WINDOW)) { - s_X11.destroyWindow(s_X11.display, window); - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - s_X11.changeProperty( - s_X11.display, - window, - s_X11Atoms._NET_WM_WINDOW_TYPE, - XA_ATOM, - 32, - PropModeReplace, - (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY, - 1); - } - - // topmost - if (info->style & PAL_WINDOW_STYLE_TOPMOST) { - if (s_X11Atoms._NET_WM_STATE_ABOVE) { - s_X11.changeProperty( - s_X11.display, - window, - s_X11Atoms._NET_WM_STATE, - XA_ATOM, - 32, - PropModeAppend, - (unsigned char*)&s_X11Atoms._NET_WM_STATE_ABOVE, - 1); - } - } - - // set size hints - XSizeHints wmHints = {0}; - wmHints.flags = PPosition | PSize; - wmHints.x = x; - wmHints.y = y; - wmHints.width = info->width; - wmHints.height = info->height; - - // resizable - if (!(info->style & PAL_WINDOW_STYLE_RESIZABLE)) { - wmHints.flags |= PMinSize; - wmHints.flags |= PMaxSize; - wmHints.min_width = wmHints.max_width = info->width; - wmHints.min_height = wmHints.max_height = info->height; - } - - // show window - s_X11.setWMNormalHints(s_X11.display, window, &wmHints); - if (info->show) { - s_X11.mapWindow(s_X11.display, window); - s_X11.flush(s_X11.display); - } - - // check if the window has been mapped - // we use this to minimize or maximize - XWindowAttributes attr; - s_X11.getWindowAttributes(s_X11.display, window, &attr); - bool windowMapped = attr.map_state = IsViewable; - - // maximize - if (info->maximized) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - s_X11.destroyWindow(s_X11.display, window); - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - // if the window is not mapped, we wait till its mapped - if (!windowMapped) { - // wait till the window is mapped - for (;;) { - XEvent event; - s_X11.nextEvent(s_X11.display, &event); - if (event.type == MapNotify && event.xmap.window == window) { - windowMapped = true; - break; - } - } - } - - xSendWMEvent( - window, - s_X11Atoms._NET_WM_STATE, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, - 1, - 0, - true); // _NET_WM_STATE_ADD - } - - // minimize - if (info->minimized) { - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - s_X11.destroyWindow(s_X11.display, window); - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - // if the window is not mapped, we wait till its mapped - if (!windowMapped) { - // wait till the window is mapped - for (;;) { - XEvent event; - s_X11.nextEvent(s_X11.display, &event); - if (event.type == MapNotify && event.xmap.window == window) { - windowMapped = true; - break; - } - } - } - s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); - } - - s_X11 - .setWMProtocols(s_X11.display, window, &s_X11Atoms.WM_DELETE_WINDOW, 1); - - s_X11.flush(s_X11.display); - - // attach the window data to the window - data->skipConfigure = true; - data->skipState = true; - data->isAttached = false; // true for attached windows - data->dpi = dpi; // the current window monitor - data->window = TO_PAL_HANDLE(PalWindow, window); - s_X11.saveContext(s_X11.display, window, s_X11.dataID, (XPointer)data); - - // create an input context - data->ic = s_X11.createIC( - s_X11.im, - XNInputStyle, - XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, - window, - XNFocusWindow, - window, - nullptr); - - if (!data->ic) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - *outWindow = TO_PAL_HANDLE(PalWindow, window); - return PAL_RESULT_SUCCESS; -} - -static void xDestroyWindow(PalWindow* window) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - WindowData* data = nullptr; - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); - - // PAL does not destroy an attached window - if (data->isAttached) { - return; - } - - s_X11.destroyIC(data->ic); - s_X11.destroyWindow(s_X11.display, xWin); - - if (data->colormap != None) { - s_X11.freeColormap(s_X11.display, data->colormap); - } - - data->used = false; -} - -PalResult xMinimizeWindow(PalWindow* window) -{ - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - s_X11.iconifyWindow(s_X11.display, xWin, s_X11.screen); - return PAL_RESULT_SUCCESS; -} - -PalResult xMaximizeWindow(PalWindow* window) -{ - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - xSendWMEvent( - xWin, - s_X11Atoms._NET_WM_STATE, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, - 1, - 0, - true); // _NET_WM_STATE_ADD - - return PAL_RESULT_SUCCESS; -} - -PalResult xRestoreWindow(PalWindow* window) -{ - if (!(s_Video.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - // since we have no fixed way to restore the window - // we just restore from minimized and maximized state - xSendWMEvent( - xWin, - s_X11Atoms._NET_WM_STATE, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, - s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, - 1, - 0, - false); // _NET_WM_STATE_REMOVE - - s_X11.mapRaised(s_X11.display, xWin); - - return PAL_RESULT_SUCCESS; -} - -PalResult xShowWindow(PalWindow* window) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - s_X11.mapWindow(s_X11.display, xWin); - return PAL_RESULT_SUCCESS; -} - -PalResult xHideWindow(PalWindow* window) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - s_X11.unmapWindow(s_X11.display, xWin); - return PAL_RESULT_SUCCESS; -} - -PalResult xFlashWindow( - PalWindow* window, - const PalFlashInfo* info) -{ - if (info->flags & PAL_FLASH_CAPTION) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - bool add = false; - if (info->flags & PAL_FLASH_TRAY) { - add = true; - } - - // check if modern flashing is supported - if (s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS) { - xSendWMEvent( - xWin, - s_X11Atoms._NET_WM_STATE, - s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS, - 0, - 0, - 0, - add); // _NET_WM_STATE_ADD - - } else { - // legacy mode - XWMHints* hints = s_X11.getWMHints(s_X11.display, xWin); - if (!hints) { - hints = s_X11.allocWMHints(); - if (!hints) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - if (add) { - hints->flags |= XUrgencyHint; - } else { - hints->flags &= ~XUrgencyHint; - } - s_X11.setWMHints(s_X11.display, xWin, hints); - s_X11.free(hints); - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle) -{ - // Window Manager quirks - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult xGetWindowMonitor( - PalWindow* window, - PalMonitor** outMonitor) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult xGetWindowTitle( - PalWindow* window, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (!outBuffer || bufferSize <= 0) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - if (s_X11Atoms.unicodeTitle) { - Atom type; - int format; - unsigned long count, bytesAfter; - unsigned char* prop = nullptr; - s_X11.getWindowProperty( - s_X11.display, - xWin, - s_X11Atoms._NET_WM_NAME, - 0, - (~0L), - False, - s_X11Atoms.UTF8_STRING, - &type, - &format, - &count, - &bytesAfter, - &prop); - - if (bufferSize >= count) { - strcpy(outBuffer, (const char*)prop); - } else { - // copy up to the limiit of the supplied buffer - strncpy(outBuffer, (const char*)prop, bufferSize); - } - s_X11.free(prop); - - } else { - XTextProperty text; - if (!s_X11.getWMName(s_X11.display, xWin, &text)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (bufferSize >= text.nitems) { - strcpy(outBuffer, (const char*)text.value); - } else { - // copy up to the limiit of the supplied buffer - strncpy(outBuffer, (const char*)text.value, bufferSize); - } - s_X11.free(text.value); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (x) { - *x = attr.x; - } - - if (y) { - *y = attr.y; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (width) { - *width = attr.width; - } - - if (height) { - *height = attr.height; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xGetWindowState( - PalWindow* window, - PalWindowState* outState) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - Atom type; - int format; - unsigned long count, bytesAfter; - unsigned char* props = nullptr; - s_X11.getWindowProperty( - s_X11.display, - xWin, - s_X11Atoms._NET_WM_STATE, - 0, - (~0L), - False, - XA_ATOM, - &type, - &format, - &count, - &bytesAfter, - &props); - - PalWindowState state = PAL_WINDOW_STATE_RESTORED; - for (unsigned long i = 0; i < count; ++i) { - if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } - - if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { - state = PAL_WINDOW_STATE_MAXIMIZED; - } - - if (props[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { - state = PAL_WINDOW_STATE_MINIMIZED; - } - } - - s_X11.free(props); - *outState = state; - return PAL_RESULT_SUCCESS; -} - -bool xIsWindowVisible(PalWindow* window) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return false; - } - - return attr.map_state == IsViewable; -} - -PalWindow* xGetFocusWindow() -{ - Window window; - int tmp; - s_X11.getInputFocus(s_X11.display, &window, &tmp); - Window xWin = FROM_PAL_HANDLE(Window, window); - - if (xWin == s_X11.root) { - return nullptr; - } - return TO_PAL_HANDLE(PalWindow, window); -} - -PalWindowHandleInfo xGetWindowHandleInfo(PalWindow* window) -{ - PalWindowHandleInfo info; - info.nativeDisplay = (void*)s_X11.display; - info.nativeWindow = (void*)window; - return info; -} - -PalWindowHandleInfoEx xGetWindowHandleInfoEx(PalWindow* w) -{ - PalWindowHandleInfoEx info = {0}; - info.nativeDisplay = (void*)s_X11.display; - info.nativeWindow = (void*)w; - return info; -} - -static PalResult xSetWindowOpacity( - PalWindow* window, - float opacity) -{ - XErrorHandler old = s_X11.setErrorHandler(xErrorHandler); - unsigned long value = (unsigned long)(opacity * 0xFFFFFFFFUL + 0.5f); - - s_X11.changeProperty( - s_X11.display, - FROM_PAL_HANDLE(Window, window), - s_X11Atoms._NET_WM_WINDOW_OPACITY, - XA_CARDINAL, - 32, - PropModeReplace, - (unsigned char*)&value, - 1); - - s_X11.sync(s_X11.display, False); - s_X11.setErrorHandler(old); - if (s_X11.error) { - // technically, this is the only error that can occur - return PAL_RESULT_INVALID_WINDOW; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xSetWindowStyle( - PalWindow* window, - PalWindowStyle style) -{ - // Window Manager quirks - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult xSetWindowTitle( - PalWindow* window, - const char* title) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (s_X11Atoms.unicodeTitle) { - s_X11.changeProperty( - s_X11.display, - xWin, - s_X11Atoms._NET_WM_NAME, - s_X11Atoms.UTF8_STRING, - 8, // unsigned char - PropModeReplace, - title, - strlen(title)); - - } else { - s_X11.storeName(s_X11.display, xWin, title); - } - - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - s_X11.moveWindow(s_X11.display, xWin, x, y); - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xSetWindowSize( - PalWindow* window, - Uint32 width, - Uint32 height) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - // X11 does not allow users resize programaticaly - // if the window is not resizable. - // so we hack it by making the window resizable and resizing - // then revert back. - XSizeHints hints; - long tmp; - s_X11.getWMNormalHints(s_X11.display, xWin, &hints, &tmp); - if ((hints.flags & PMinSize) && (hints.flags & PMaxSize)) { - hints.flags &= ~PMinSize; - hints.flags &= ~PMaxSize; - s_X11.resizeWindow(s_X11.display, xWin, width, height); - s_X11.flush(s_X11.display); - - // revert - hints.flags |= PMinSize; - hints.flags |= PMaxSize; - hints.min_width = hints.max_width = width; - hints.min_height = hints.max_height = height; - - } else { - // window is already resizable - s_X11.resizeWindow(s_X11.display, xWin, width, height); - } - - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xSetFocusWindow(PalWindow* window) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (s_X11Atoms._NET_ACTIVE_WINDOW) { - xSendWMEvent( - xWin, - s_X11Atoms._NET_ACTIVE_WINDOW, - CurrentTime, - 0, - 0, - 0, - true); // 1 - - } else { - s_X11.setInputFocus(s_X11.display, xWin, RevertToParent, CurrentTime); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon) -{ - Uint64 totalPixels = 2 + (Uint64)(info->width * info->height); - Uint64 totalBytes = sizeof(unsigned long) * totalPixels; - - unsigned long* icon = palAllocate(s_Video.allocator, totalBytes, 0); - if (!icon) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - // store width and height and populate data with icon pixels - // [width][height][pixels] - icon[0] = (unsigned long)info->width; - icon[1] = (unsigned long)info->height; - - // convert from RGBA8 to ARGB32 - for (int i = 0; i < info->width * info->height; i++) { - Uint8 r = info->pixels[i * 4 + 0]; // Red - Uint8 g = info->pixels[i * 4 + 1]; // Green - Uint8 b = info->pixels[i * 4 + 2]; // Blue - Uint8 a = info->pixels[i * 4 + 3]; // Alpha - - // clang-format off - icon[2 + i] = ((unsigned long)a << 24) | - ((unsigned long)r << 16) | - ((unsigned long)g << 8) | - ((unsigned long)b); - // clang-format on - } - - *outIcon = TO_PAL_HANDLE(PalIcon, icon); - return PAL_RESULT_SUCCESS; -} - -void xDestroyIcon(PalIcon* icon) -{ - if (icon) { - palFree(s_Video.allocator, icon); - } -} - -PalResult xSetWindowIcon( - PalWindow* window, - PalIcon* icon) -{ - WindowData* winData = nullptr; - Window xWin = FROM_PAL_HANDLE(Window, window); - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&winData); - if (!winData) { - return PAL_RESULT_INVALID_WINDOW; - } - - unsigned long* iconData = FROM_PAL_HANDLE(unsigned long*, icon); - Uint64 totalPixels = 2 + iconData[0] * iconData[1]; - s_X11.changeProperty( - s_X11.display, - xWin, - s_X11Atoms._NET_WM_ICON, - XA_CARDINAL, - 32, - PropModeReplace, - (unsigned char*)iconData, - (int)totalPixels); - - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor) -{ - XcursorImage* image = s_X11.cursorImageCreate(info->width, info->height); - image->xhot = info->xHotspot; - image->yhot = info->yHotspot; - - // convert from RGBA8 to ARGB32 - for (int i = 0; i < info->width * info->height; i++) { - Uint8 r = info->pixels[i * 4 + 0]; // Red - Uint8 g = info->pixels[i * 4 + 1]; // Green - Uint8 b = info->pixels[i * 4 + 2]; // Blue - Uint8 a = info->pixels[i * 4 + 3]; // Alpha - - // clang-format off - image->pixels[i] = ((unsigned long)a << 24) | - ((unsigned long)r << 16) | - ((unsigned long)g << 8) | - ((unsigned long)b); - // clang-format on - } - - Cursor cursor = s_X11.cursorImageLoadCursor(s_X11.display, image); - if (!cursor) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_X11.cursorImageDestroy(image); - *outCursor = TO_PAL_HANDLE(PalCursor, cursor); - return PAL_RESULT_SUCCESS; -} - -PalResult xCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor) -{ - int shape; - Cursor cursor; - switch (type) { - case PAL_CURSOR_ARROW: { - shape = XC_left_ptr; - break; - } - - case PAL_CURSOR_HAND: { - shape = XC_hand2; - break; - } - - case PAL_CURSOR_CROSS: { - shape = XC_cross; - break; - } - - case PAL_CURSOR_IBEAM: { - shape = XC_xterm; - break; - } - - case PAL_CURSOR_WAIT: { - shape = XC_watch; - break; - } - - default: { - return PAL_RESULT_INVALID_ARGUMENT; - } - } - - cursor = s_X11.createFontCursor(s_X11.display, shape); - *outCursor = TO_PAL_HANDLE(PalCursor, cursor); - return PAL_RESULT_SUCCESS; -} - -void xDestroyCursor(PalCursor* cursor) -{ - s_X11.freeCursor(s_X11.display, FROM_PAL_HANDLE(Cursor, cursor)); -} - -void xShowCursor(bool show) -{ - // x11 does not support Hiding and showing cursor - return; -} - -PalResult xClipCursor( - PalWindow* window, - bool clip) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (clip) { - s_X11.grabPointer( - s_X11.display, - xWin, - True, - 0, - GrabModeAsync, - GrabModeAsync, - xWin, - None, - CurrentTime); - - } else { - s_X11.ungrabPointer(s_X11.display, CurrentTime); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xGetCursorPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - Window root, rootChild; - int rootX, rootY, winX, winY; - unsigned int mask; - s_X11.queryPointer( - s_X11.display, - xWin, - &root, - &rootChild, - &rootX, - &rootY, - &winX, - &winY, - &mask); - - if (x) { - *x = winX; - } - - if (y) { - *y = winY; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult xSetCursorPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - s_X11.warpPointer(s_X11.display, None, xWin, 0, 0, 0, 0, x, y); - - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xSetWindowCursor( - PalWindow* window, - PalCursor* cursor) -{ - Window xWin = FROM_PAL_HANDLE(Window, window); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - Window xCursor = FROM_PAL_HANDLE(Cursor, cursor); - if (xCursor) { - s_X11.defineCursor(s_X11.display, xWin, xCursor); - - } else { - s_X11.undefineCursor(s_X11.display, xWin); - } - - s_X11.flush(s_X11.display); - return PAL_RESULT_SUCCESS; -} - -PalResult xAttachWindow( - void* windowHandle, - PalWindow** outWindow) -{ - Window xWin = FROM_PAL_HANDLE(Window, windowHandle); - XWindowAttributes attr; - if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { - return PAL_RESULT_INVALID_WINDOW; - } - - // get a free slot and set the window handle to it - // we also set a flag to make sure we know this is an attached window - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); - // we assume the window was just created, since there is - // no official way to get the DPI - data->isAttached = true; - data->dpi = 96; // if this is not the DPI, a dpi event will be triggered - - // If the window manager has not mapped the window yet, - // we dont need the initial Size / Move events - data->skipConfigure = true; - data->skipState = true; - data->skipIfAttached = true; - data->window = window; - data->w = attr.width; - data->h = attr.height; - data->x = attr.x; - data->y = attr.y; - - // get the current window state - // we dont check the return code because we know the window is valid - xGetWindowState(window, &data->state); - - // listen to the events we support - long mask = StructureNotifyMask | KeyPressMask; - mask |= KeyReleaseMask; - mask |= ButtonPressMask; - mask |= ButtonReleaseMask; - mask |= PointerMotionMask; - mask |= FocusChangeMask; - mask |= PropertyChangeMask; - s_X11.selectInput(s_X11.display, xWin, mask); - - // listen to window close event - s_X11.setWMProtocols(s_X11.display, xWin, &s_X11Atoms.WM_DELETE_WINDOW, 1); - - s_X11.saveContext(s_X11.display, xWin, s_X11.dataID, (XPointer)data); - s_X11.flush(s_X11.display); - - *outWindow = window; - return PAL_RESULT_SUCCESS; -} - -PalResult xDetachWindow( - PalWindow* window, - void** outWindowHandle) -{ - // we check is the window is really detachable - Window xWin = FROM_PAL_HANDLE(Window, window); - WindowData* data = nullptr; - s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (data->isAttached == false) { - // window was created by PAL - return PAL_RESULT_INVALID_WINDOW; - } - - // detach the window - data->used = false; - long mask = 0; - s_X11.selectInput(s_X11.display, xWin, mask); - s_X11.setWMProtocols(s_X11.display, xWin, nullptr, 0); - - if (outWindowHandle) { - *outWindowHandle = (void*)window; - } - - return PAL_RESULT_SUCCESS; -} - -static Backend s_XBackend = { - .shutdownVideo = xShutdownVideo, - .updateVideo = xUpdateVideo, - .setFBConfig = xSetFBConfig, - .enumerateMonitors = xEnumerateMonitors, - .getMonitorInfo = xGetMonitorInfo, - .getPrimaryMonitor = xGetPrimaryMonitor, - .enumerateMonitorModes = xEnumerateMonitorModes, - .getCurrentMonitorMode = xGetCurrentMonitorMode, - .setMonitorMode = xSetMonitorMode, - .validateMonitorMode = xValidateMonitorMode, - .setMonitorOrientation = xSetMonitorOrientation, - - .createWindow = xCreateWindow, - .destroyWindow = xDestroyWindow, - .maximizeWindow = xMaximizeWindow, - .minimizeWindow = xMinimizeWindow, - .restoreWindow = xRestoreWindow, - .showWindow = xShowWindow, - .hideWindow = xHideWindow, - .flashWindow = xFlashWindow, - .getWindowStyle = xGetWindowStyle, - .getWindowMonitor = xGetWindowMonitor, - .getWindowTitle = xGetWindowTitle, - .getWindowPos = xGetWindowPos, - .getWindowSize = xGetWindowSize, - .getWindowState = xGetWindowState, - .isWindowVisible = xIsWindowVisible, - .getFocusWindow = xGetFocusWindow, - .getWindowHandleInfo = xGetWindowHandleInfo, - .getWindowHandleInfoEx = xGetWindowHandleInfoEx, - .setWindowOpacity = xSetWindowOpacity, - .setWindowStyle = xSetWindowStyle, - .setWindowTitle = xSetWindowTitle, - .setWindowPos = xSetWindowPos, - .setWindowSize = xSetWindowSize, - .setFocusWindow = xSetFocusWindow, - - .createIcon = xCreateIcon, - .destroyIcon = xDestroyIcon, - .setWindowIcon = xSetWindowIcon, - - .createCursor = xCreateCursor, - .createCursorFrom = xCreateCursorFrom, - .destroyCursor = xDestroyCursor, - .showCursor = xShowCursor, - .clipCursor = xClipCursor, - .getCursorPos = xGetCursorPos, - .setCursorPos = xSetCursorPos, - .setWindowCursor = xSetWindowCursor, - - .attachWindow = xAttachWindow, - .detachWindow = xDetachWindow}; - -#endif // PAL_HAS_X11 -#pragma endregion - -// ================================================== -// Wayland API -// ================================================== - -#pragma region Wayland API -#if PAL_HAS_WAYLAND - -PalResult eglWlBackend(const int index) -{ - // user choose EGL FBConfig backend - if (!s_Egl.handle) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLDisplay display = EGL_NO_DISPLAY; - display = s_Egl.eglGetDisplay((EGLNativeDisplayType)s_Wl.display); - - if (display == EGL_NO_DISPLAY) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint numConfigs = 0; - if (!s_Egl.eglGetConfigs(display, nullptr, 0, &numConfigs)) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - EGLint configSize = sizeof(EGLConfig) * numConfigs; - EGLConfig* eglConfigs = palAllocate(s_Video.allocator, configSize, 0); - if (!eglConfigs) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_Egl.eglGetConfigs(display, eglConfigs, numConfigs, &numConfigs); - s_Wl.eglFBConfig = eglConfigs[index]; - - return PAL_RESULT_SUCCESS; -} - -static void wlCreateKeycodeTable() -{ - // Tis is for only printable and text input keys - - // Letters - s_Keyboard.keycodes[XKB_KEY_a] = PAL_KEYCODE_A; - s_Keyboard.keycodes[XKB_KEY_b] = PAL_KEYCODE_B; - s_Keyboard.keycodes[XKB_KEY_c] = PAL_KEYCODE_C; - s_Keyboard.keycodes[XKB_KEY_d] = PAL_KEYCODE_D; - s_Keyboard.keycodes[XKB_KEY_e] = PAL_KEYCODE_E; - s_Keyboard.keycodes[XKB_KEY_f] = PAL_KEYCODE_F; - s_Keyboard.keycodes[XKB_KEY_g] = PAL_KEYCODE_G; - s_Keyboard.keycodes[XKB_KEY_h] = PAL_KEYCODE_H; - s_Keyboard.keycodes[XKB_KEY_i] = PAL_KEYCODE_I; - s_Keyboard.keycodes[XKB_KEY_j] = PAL_KEYCODE_J; - s_Keyboard.keycodes[XKB_KEY_k] = PAL_KEYCODE_K; - s_Keyboard.keycodes[XKB_KEY_l] = PAL_KEYCODE_L; - s_Keyboard.keycodes[XKB_KEY_m] = PAL_KEYCODE_M; - s_Keyboard.keycodes[XKB_KEY_n] = PAL_KEYCODE_N; - s_Keyboard.keycodes[XKB_KEY_o] = PAL_KEYCODE_O; - s_Keyboard.keycodes[XKB_KEY_p] = PAL_KEYCODE_P; - s_Keyboard.keycodes[XKB_KEY_q] = PAL_KEYCODE_Q; - s_Keyboard.keycodes[XKB_KEY_r] = PAL_KEYCODE_R; - s_Keyboard.keycodes[XKB_KEY_s] = PAL_KEYCODE_S; - s_Keyboard.keycodes[XKB_KEY_t] = PAL_KEYCODE_T; - s_Keyboard.keycodes[XKB_KEY_u] = PAL_KEYCODE_U; - s_Keyboard.keycodes[XKB_KEY_v] = PAL_KEYCODE_V; - s_Keyboard.keycodes[XKB_KEY_w] = PAL_KEYCODE_W; - s_Keyboard.keycodes[XKB_KEY_x] = PAL_KEYCODE_X; - s_Keyboard.keycodes[XKB_KEY_y] = PAL_KEYCODE_Y; - s_Keyboard.keycodes[XKB_KEY_z] = PAL_KEYCODE_Z; - - // Control - s_Keyboard.keycodes[XKB_KEY_space] = PAL_KEYCODE_SPACE; - - // Misc - s_Keyboard.keycodes[XKB_KEY_apostrophe] = PAL_KEYCODE_APOSTROPHE; - s_Keyboard.keycodes[XKB_KEY_backslash] = PAL_KEYCODE_BACKSLASH; - s_Keyboard.keycodes[XKB_KEY_comma] = PAL_KEYCODE_COMMA; - s_Keyboard.keycodes[XKB_KEY_equal] = PAL_KEYCODE_EQUAL; - s_Keyboard.keycodes[XKB_KEY_grave] = PAL_KEYCODE_GRAVEACCENT; - s_Keyboard.keycodes[XKB_KEY_minus] = PAL_KEYCODE_SUBTRACT; - s_Keyboard.keycodes[XKB_KEY_period] = PAL_KEYCODE_PERIOD; - s_Keyboard.keycodes[XKB_KEY_semicolon] = PAL_KEYCODE_SEMICOLON; - s_Keyboard.keycodes[XKB_KEY_slash] = PAL_KEYCODE_SLASH; - s_Keyboard.keycodes[XKB_KEY_bracketleft] = PAL_KEYCODE_LBRACKET; - s_Keyboard.keycodes[XKB_KEY_bracketright] = PAL_KEYCODE_RBRACKET; -} - -static int createShmFile(Uint64 size) -{ - char template[] = "/tmp/pal-shm-XXXXXX"; - int fd = mkstemp(template); - if (fd < 0) { - return -1; - } - - unlink(template); - if (ftruncate(fd, size) < 0) { - return -1; - } - - return fd; -} - -static struct wl_buffer* createShmBuffer( - int width, - int height, - const Uint8* pixels, - bool cursor) -{ - int stride = width * 4; - Uint64 size = stride * height; - struct wl_buffer* buffer = nullptr; - struct wl_shm_pool* pool = nullptr; - void* data = nullptr; - - int fd = createShmFile(size); - if (fd == -1) { - return nullptr; - } - - data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (data == MAP_FAILED) { - return nullptr; - } - - enum wl_shm_format format = WL_SHM_FORMAT_XRGB8888; - if (cursor) { - format = WL_SHM_FORMAT_ARGB8888; - Uint32* dataPixels = (Uint32*)data; - - // convert from RGBA8 to ARGB32 - for (int i = 0; i < width * height; i++) { - Uint8 r = pixels[i * 4 + 0]; // Red - Uint8 g = pixels[i * 4 + 1]; // Green - Uint8 b = pixels[i * 4 + 2]; // Blue - Uint8 a = pixels[i * 4 + 3]; // Alpha - - // clang-format off - dataPixels[i] = ((unsigned long)a << 24) | - ((unsigned long)r << 16) | - ((unsigned long)g << 8) | - ((unsigned long)b); - // clang-format on - } - - } else { - memset(data, 0xFF, size); // white - } - - pool = wlShmCreatePool(s_Wl.shm, fd, size); - if (!pool) { - return nullptr; - } - - buffer = wlShmPoolCreateBuffer(pool, 0, width, height, stride, format); - - if (!buffer) { - return nullptr; - } - - wlShmPoolDestroy(pool); - munmap(data, size); - close(fd); - return buffer; -} - -static void wlOutputGeometry( - void* data, - struct wl_output* output, - int32_t x, - int32_t y, - int32_t, // we dont need physical size - int32_t, // we dont need physical size - int32_t, // we dont need subpixel - const char* make, - const char* model, - int32_t transform) -{ - MonitorData* monitorData = data; - monitorData->x = x; - monitorData->y = y; - - switch (transform) { - case WL_OUTPUT_TRANSFORM_NORMAL: - case WL_OUTPUT_TRANSFORM_180: { - monitorData->orientation = PAL_ORIENTATION_LANDSCAPE; - break; - } - - case WL_OUTPUT_TRANSFORM_90: - case WL_OUTPUT_TRANSFORM_270: { - monitorData->orientation = PAL_ORIENTATION_PORTRAIT; - break; - } - - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_FLIPPED_180: { - monitorData->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; - break; - } - - case WL_OUTPUT_TRANSFORM_FLIPPED_90: - case WL_OUTPUT_TRANSFORM_FLIPPED_270: { - monitorData->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; - break; - } - } - - snprintf(monitorData->name, 32, "%s %s", make, model); -} - -static void wlOutputMode( - void* data, - struct wl_output* output, - uint32_t flags, - int32_t width, - int32_t height, - int32_t refresh) -{ - MonitorData* monitorData = data; - if (flags & WL_OUTPUT_MODE_CURRENT) { - monitorData->w = width; - monitorData->h = height; - monitorData->refreshRate = (refresh + 500) / 1000; - - // wayland only sends the current mode - monitorData->mode.bpp = 0; - monitorData->mode.width = width; - monitorData->mode.height = height; - monitorData->mode.refreshRate = (refresh + 500) / 1000; - } -} - -static void wlOutputScale( - void* data, - struct wl_output* output, - int32_t scale) -{ - MonitorData* monitorData = data; - float dpi = (float)scale * 96.0f; - monitorData->dpi = (Uint32)dpi; -} - -static void wlOutputDone( - void* data, - struct wl_output* output) -{ -} - -static const struct wl_output_listener s_OutputListener = { - .geometry = wlOutputGeometry, - .mode = wlOutputMode, - .done = wlOutputDone, - .scale = wlOutputScale}; - -static const struct wl_output_listener s_DefaultModeListener = { - .geometry = wlOutputGeometry, - .mode = wlOutputMode, - .done = wlOutputDone, - .scale = wlOutputScale}; - -static void globalHandle( - void* data, - struct wl_registry* registry, - uint32_t name, - const char* interface, - uint32_t version) -{ - if (s_Wl.checkFeatures) { - PalVideoFeatures features = 0; - PalVideoFeatures64 features64 = 0; - features |= PAL_VIDEO_FEATURE_HIGH_DPI; - features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; - features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; - features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; - features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; - - features64 |= PAL_VIDEO_FEATURE64_HIGH_DPI; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; - features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; - features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STATE; - features64 |= PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW; - - features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; - - s_Video.features = features; - s_Video.features64 = features64; - s_Wl.checkFeatures = false; - } - - if (strcmp(interface, "wl_compositor") == 0) { - s_Wl.compositor = - wlRegistryBind(registry, name, s_Wl.compositorInterface, 4); - - } else if (strcmp(interface, "xdg_wm_base") == 0) { - s_Wl.xdgBase = - wlRegistryBind(registry, name, &xdg_wm_base_interface, 1); - - xdgWmBaseAddListener(s_Wl.xdgBase, &wmBaseListener, nullptr); - - } else if (strcmp(interface, "wl_shm") == 0) { - s_Wl.shm = wlRegistryBind(registry, name, s_Wl.shmInterface, 1); - - } else if (strcmp(interface, "wl_seat") == 0) { - s_Wl.seat = wlRegistryBind(registry, name, s_Wl.seatInterface, 5); - - wlSeatAddListener(s_Wl.seat, &seatListener, nullptr); - - } else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) { - s_Wl.decorationManager = wlRegistryBind( - registry, - name, - &zxdg_decoration_manager_v1_interface, - 1); - - s_Video.features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; - - } else if (strcmp(interface, "wl_output") == 0) { - // wayland does not let use query monitors directly - // so we enumerate and store at init and update the - // cache when a monitor is added or removed - MonitorData* monitorData = getFreeMonitorData(); - if (!monitorData) { - return; - } - - struct wl_output* output = nullptr; - output = wlRegistryBind(s_Wl.registry, name, s_Wl.outputInterface, 3); - wlOutputAddListener(output, &s_OutputListener, monitorData); - - monitorData->wlName = name; - monitorData->monitor = (PalMonitor*)output; - s_Wl.monitorCount++; - } -} - -static void globalRemove( - void* data, - struct wl_registry* registry, - uint32_t name) -{ - for (int i = 0; i < s_Video.maxMonitorData; ++i) { - if (s_Video.monitorData[i].used && - s_Video.monitorData[i].wlName == name) { - MonitorData* data = &s_Video.monitorData[i]; - data->used = false; - s_Wl.proxyDestroy((struct wl_proxy*)data->monitor); - s_Wl.monitorCount--; - } - } -} - -static const struct wl_registry_listener s_RegistryListener = { - .global = globalHandle, - .global_remove = globalRemove}; - -PalResult wlInitVideo() -{ - // load wayland libray - s_Wl.handle = dlopen("libwayland-client.so.0", RTLD_LAZY); - s_Wl.xkbCommon = dlopen("libxkbcommon.so", RTLD_LAZY); - s_Wl.libCursor = dlopen("libwayland-cursor.so", RTLD_LAZY); - s_Wl.libWaylandEgl = dlopen("libwayland-egl.so", RTLD_LAZY); - - // clang-format off - if (!s_Wl.handle || - !s_Wl.xkbCommon || - !s_Wl.libCursor || - !s_Wl.libWaylandEgl) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // get the exported global variables - s_Wl.outputInterface = dlsym(s_Wl.handle, "wl_output_interface"); - s_Wl.seatInterface = dlsym(s_Wl.handle, "wl_seat_interface"); - s_Wl.compositorInterface = dlsym(s_Wl.handle, "wl_compositor_interface"); - s_Wl.surfaceInterface = dlsym(s_Wl.handle, "wl_surface_interface"); - s_Wl.registryInterface = dlsym(s_Wl.handle, "wl_registry_interface"); - s_Wl.shmInterface = dlsym(s_Wl.handle, "wl_shm_interface"); - s_Wl.bufferInterface = dlsym(s_Wl.handle, "wl_buffer_interface"); - s_Wl.shmPoolInterface = dlsym(s_Wl.handle, "wl_shm_pool_interface"); - s_Wl.regionInterface = dlsym(s_Wl.handle, "wl_region_interface"); - s_Wl.pointerInterface = dlsym(s_Wl.handle, "wl_pointer_interface"); - s_Wl.keyboardInterface = dlsym(s_Wl.handle, "wl_keyboard_interface"); - - // load function procs - s_Wl.displayConnect = (wl_display_connect_fn)dlsym( - s_Wl.handle, - "wl_display_connect"); - - s_Wl.displayDisconnect = (wl_display_disconnect_fn)dlsym( - s_Wl.handle, - "wl_display_disconnect"); - - s_Wl.displayRoundtrip = (wl_display_roundtrip_fn)dlsym( - s_Wl.handle, - "wl_display_roundtrip"); - - s_Wl.displayDispatch = (wl_display_dispatch_fn)dlsym( - s_Wl.handle, - "wl_display_dispatch"); - - s_Wl.proxyAddListener = (wl_proxy_add_listener_fn)dlsym( - s_Wl.handle, - "wl_proxy_add_listener"); - - s_Wl.proxyMarshalCnstructor = (wl_proxy_marshal_constructor_v_fn)dlsym( - s_Wl.handle, - "wl_proxy_marshal_constructor_versioned"); - - s_Wl.proxyDestroy = (wl_proxy_destroy_fn)dlsym( - s_Wl.handle, - "wl_proxy_destroy"); - - s_Wl.proxyMarshalFlags = (wl_proxy_marshal_flags_fn)dlsym( - s_Wl.handle, - "wl_proxy_marshal_flags"); - - s_Wl.proxyGetVersion = (wl_proxy_get_version_fn)dlsym( - s_Wl.handle, - "wl_proxy_get_version"); - - s_Wl.getError = (wl_display_get_error_fn)dlsym( - s_Wl.handle, - "wl_display_get_error"); - - s_Wl.dispatchPending = (wl_display_dispatch_pending_fn)dlsym( - s_Wl.handle, - "wl_display_dispatch_pending"); - - s_Wl.displayFlush = (wl_display_flush_fn)dlsym( - s_Wl.handle, - "wl_display_flush"); - - s_Wl.prepareRead = (wl_display_prepare_read_fn)dlsym( - s_Wl.handle, - "wl_display_prepare_read"); - - s_Wl.readEvents = (wl_display_read_events_fn)dlsym( - s_Wl.handle, - "wl_display_read_events"); - - s_Wl.displayGetFd = (wl_display_get_fd_fn)dlsym( - s_Wl.handle, - "wl_display_get_fd"); - - s_Wl.cancelRead = (wl_display_cancel_read_fn)dlsym( - s_Wl.handle, - "wl_display_cancel_read"); - - // load xkbcommon procs - s_Wl.xkbKeymapUnref = (xkb_keymap_unref_fn)dlsym( - s_Wl.xkbCommon, - "xkb_keymap_unref"); - - s_Wl.xkbStateKeyGetOneSym = (xkb_state_key_get_one_sym_fn)dlsym( - s_Wl.xkbCommon, - "xkb_state_key_get_one_sym"); - - s_Wl.xkbStateNew = (xkb_state_new_fn)dlsym( - s_Wl.xkbCommon, - "xkb_state_new"); - - s_Wl.xkbStateUnref = (xkb_state_unref_fn)dlsym( - s_Wl.xkbCommon, - "xkb_state_unref"); - - s_Wl.xkbContextUnref = (xkb_context_unref_fn)dlsym( - s_Wl.xkbCommon, - "xkb_context_unref"); - - s_Wl.xkbContextNew = (xkb_context_new_fn)dlsym( - s_Wl.xkbCommon, - "xkb_context_new"); - - s_Wl.xkbKeymapNewFromString = (xkb_keymap_new_from_string_fn)dlsym( - s_Wl.xkbCommon, - "xkb_keymap_new_from_string"); - - s_Wl.xkbKeysymToUtf32 = (xkb_keysym_to_utf32_fn)dlsym( - s_Wl.xkbCommon, - "xkb_keysym_to_utf32"); - - s_Wl.xkbStateUpdateMask = (xkb_state_update_mask_fn)dlsym( - s_Wl.xkbCommon, - "xkb_state_update_mask"); - - s_Wl.xkbKeymapKeyRepeats = (xkb_keymap_key_repeats_fn)dlsym( - s_Wl.xkbCommon, - "xkb_keymap_key_repeats"); - - // load wayland cursor procs - s_Wl.cursorImageGetBuffer = (wl_cursor_image_get_buffer_fn)dlsym( - s_Wl.libCursor, - "wl_cursor_image_get_buffer"); - - s_Wl.cursorThemeLoad = (wl_cursor_theme_load_fn)dlsym( - s_Wl.libCursor, - "wl_cursor_theme_load"); - - s_Wl.cursorThemeGetCursor = (wl_cursor_theme_get_cursor_fn)dlsym( - s_Wl.libCursor, - "wl_cursor_theme_get_cursor"); - - // wl_egl procs - s_Wl.eglWindowCreate = (wl_egl_window_create_fn)dlsym( - s_Wl.libWaylandEgl, - "wl_egl_window_create"); - - s_Wl.eglWindowDestroy = (wl_egl_window_destroy_fn)dlsym( - s_Wl.libWaylandEgl, - "wl_egl_window_destroy"); - - s_Wl.eglWindowResize = (wl_egl_window_resize_fn)dlsym( - s_Wl.libWaylandEgl, - "wl_egl_window_resize"); - - // clang-format on - - // initialize wayland - s_Wl.checkFeatures = true; - s_Wl.monitorCount = 0; - setupXdgShellProtocol(); - - // check if user supplied their own display - if (s_Video.platformInstance) { - s_Wl.display = (struct wl_display*)s_Video.platformInstance; - - } else { - s_Wl.display = s_Wl.displayConnect(nullptr); - s_Video.platformInstance = nullptr; - } - - if (!s_Wl.display) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_Video.display = (void*)s_Wl.display; - s_Wl.registry = wlDisplayGetRegistry(s_Wl.display); - wlRegistryAddListener(s_Wl.registry, &s_RegistryListener, nullptr); - s_Wl.displayRoundtrip(s_Wl.display); - - // do a roundtrip again to get remaining handles - s_Wl.displayRoundtrip(s_Wl.display); - - if (!s_Wl.compositor || !s_Wl.xdgBase || !s_Wl.shm) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // create an input context - s_Wl.inputContext = s_Wl.xkbContextNew(XKB_CONTEXT_NO_FLAGS); - if (!s_Wl.inputContext) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // get the current theme - s_Wl.cursorTheme = s_Wl.cursorThemeLoad(nullptr, 32, s_Wl.shm); - if (!s_Wl.cursorTheme) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - wlCreateKeycodeTable(); - - s_Video.display = (void*)s_Wl.display; - return PAL_RESULT_SUCCESS; -} - -void wlShutdownVideo() -{ - if (s_Wl.state) { - s_Wl.xkbStateUnref(s_Wl.state); - s_Wl.xkbKeymapUnref(s_Wl.keymap); - } - - s_Wl.xkbContextUnref(s_Wl.inputContext); - if (s_Wl.compositor) { - // if compositor was found, all this will be as well - // since we check all at init - s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.compositor); - s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.xdgBase); - s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.shm); - s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.seat); - } - - if (!s_Video.platformInstance) { - // opened by PAL - s_Wl.displayDisconnect(s_Wl.display); - } - - dlclose(s_Wl.libCursor); - dlclose(s_Wl.xkbCommon); - dlclose(s_Wl.libWaylandEgl); - dlclose(s_Wl.handle); - - memset(&s_Wl, 0, sizeof(Wayland)); -} - -PalResult wlSetFBConfig( - const int index, - PalFBConfigBackend backend) -{ - if (backend == PAL_CONFIG_BACKEND_GLES || - backend == PAL_CONFIG_BACKEND_PAL_OPENGL) { - return eglWlBackend(index); - - } else { - return PAL_RESULT_INVALID_FBCONFIG_BACKEND; - } -} - -void wlUpdateVideo() -{ - // flush pending requests - s_Mouse.tmpScrollX = 0; - s_Mouse.tmpScrollY = 0; - - // push key repeats - // we only do this if the user wants key repeat events - if (s_Keyboard.repeatKey != 0 && s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode mode = PAL_DISPATCH_NONE; - mode = palGetEventDispatchMode(driver, PAL_EVENT_KEYREPEAT); - if (mode != PAL_DISPATCH_NONE) { - // get now time and check with the key repeat time - Uint64 now = getTime(); - if (now >= s_Keyboard.timer) { - PalWindow* window = (PalWindow*)s_Wl.keyboardSurface; - PalKeycode key = s_Keyboard.repeatKey; - PalScancode scancode = s_Keyboard.repeatScancode; - - PalEvent event = {0}; - event.type = PAL_EVENT_KEYREPEAT; - event.data = palPackUint32(key, scancode); - event.data2 = palPackPointer(window); - palPushEvent(driver, &event); - s_Keyboard.timer += s_Keyboard.repeatRate; - } - } - } - - while (s_Wl.prepareRead(s_Wl.display) != 0) { - s_Wl.dispatchPending(s_Wl.display); - } - - s_Wl.displayFlush(s_Wl.display); - int fd = s_Wl.displayGetFd(s_Wl.display); - struct pollfd pfd = {fd, POLLIN, 0}; - if (poll(&pfd, 1, 0) > 0) { - // there are events ready to be read - s_Wl.readEvents(s_Wl.display); - - } else { - s_Wl.cancelRead(s_Wl.display); - } - - // dispatch events that were read - s_Wl.dispatchPending(s_Wl.display); -} - -PalResult wlEnumerateMonitors( - Int32* count, - PalMonitor** outMonitors) -{ - if (outMonitors) { - int index = 0; - int maxCount = s_Video.maxMonitorData; - for (int i = 0; i < maxCount && index < *count; i++) { - if (s_Video.monitorData[i].used) { - // found a monitor - PalMonitor* monitor = s_Video.monitorData[index].monitor; - outMonitors[index++] = monitor; - } - } - } - - if (!outMonitors) { - *count = s_Wl.monitorCount; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult wlGetPrimaryMonitor(PalMonitor** outMonitor) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info) -{ - MonitorData* monitorData = findMonitorData(monitor); - if (!monitorData) { - return PAL_RESULT_INVALID_MONITOR; - } - - info->dpi = monitorData->dpi; - info->x = monitorData->x; - info->y = monitorData->y; - info->width = monitorData->w; - info->height = monitorData->h; - info->refreshRate = monitorData->refreshRate; - info->orientation = monitorData->orientation; - - info->primary = false; // no way to query - strcpy(info->name, monitorData->name); - - return PAL_RESULT_SUCCESS; -} - -PalResult wlEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes) -{ - MonitorData* monitorData = findMonitorData(monitor); - if (!monitorData) { - return PAL_RESULT_INVALID_MONITOR; - } - - if (modes && *count > 0) { - PalMonitorMode* mode = &modes[0]; - mode->bpp = monitorData->mode.bpp; - mode->width = monitorData->mode.width; - mode->height = monitorData->mode.height; - mode->refreshRate = monitorData->mode.refreshRate; - } - - if (!modes) { - *count = 1; // wayland only gives the active mode - } - - return PAL_RESULT_SUCCESS; -} - -PalResult wlGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - MonitorData* monitorData = findMonitorData(monitor); - if (!monitorData) { - return PAL_RESULT_INVALID_MONITOR; - } - - // this is the same as the current mode - mode->bpp = monitorData->mode.bpp; - mode->width = monitorData->mode.width; - mode->height = monitorData->mode.height; - mode->refreshRate = monitorData->mode.refreshRate; - - return PAL_RESULT_SUCCESS; -} - -PalResult wlSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow) -{ - struct wl_surface* surface = nullptr; - struct xdg_surface* xdgSurface = nullptr; - struct xdg_toplevel* xdgToplevel = nullptr; - - if (info->style & PAL_WINDOW_STYLE_TOPMOST) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - if (info->style & PAL_WINDOW_STYLE_TOOL) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { - if (!s_Wl.decorationManager) { - // user wants decorated window but its not supported - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - } - - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - memset(data, 0, sizeof(WindowData)); - data->used = true; - data->focused = false; - - // create surface - surface = wlCompositorCreateSurface(s_Wl.compositor); - if (!surface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - wlSurfaceAddListener(surface, &surfaceListener, data); - xdgSurface = xdgWmBaseGetXdgSurface(s_Wl.xdgBase, surface); - if (!xdgSurface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - xdgToplevel = xdgSurfaceGetToplevel(xdgSurface); - if (!xdgSurface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // set APP id - const char* appID = getenv("RESOURCE_CLASS"); - if (!appID || strlen(appID) == 0) { - appID = s_Video.className; - } - - const char* title = info->title; - if (!title) { - title = ""; - } - - xdgToplevelSetTitle(xdgToplevel, title); - xdgToplevelSetAppId(xdgToplevel, appID); - - xdgToplevelAddListener(xdgToplevel, &xdgToplevelListener, data); - xdgSurfaceAddListener(xdgSurface, &xdgSurfaceListener, data); - - // decorated window - if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { - struct zxdg_toplevel_decoration_v1* decoration = nullptr; - decoration = - zxdgGetToplevelDecoration(s_Wl.decorationManager, xdgToplevel); - - zxdgToplevelDecorationV1AddListener( - decoration, - &decorationListener, - surface); - - zxdgToplevelDecorationV1SetMode(decoration, 2); - data->decoration = decoration; - } - - data->skipState = true; - data->skipConfigure = true; - wlSurfaceCommit(surface); - s_Wl.displayRoundtrip(s_Wl.display); - - if (info->maximized && info->show) { - // we need the maximized size the compositor will use - // and use that to create the buffer - xdgToplevelSetMaximized(xdgToplevel); - wlSurfaceCommit(surface); - s_Wl.displayRoundtrip(s_Wl.display); - data->state = PAL_WINDOW_STATE_MAXIMIZED; - - } else { - data->w = info->width; - data->h = info->height; - } - - data->xdgSurface = xdgSurface; - data->xdgToplevel = xdgToplevel; - data->window = (PalWindow*)surface; - data->buffer = nullptr; - data->isAttached = false; - data->state = PAL_WINDOW_STATE_RESTORED; - - // minimize - // This is just a requeest, the compositor might ignore it - if (info->minimized) { - xdgToplevelSetMinimized(xdgToplevel); - wlSurfaceCommit(surface); - data->state = PAL_WINDOW_STATE_MINIMIZED; - } - - if (s_Wl.eglFBConfig) { - data->eglWindow = s_Wl.eglWindowCreate(surface, data->w, data->h); - if (!data->eglWindow) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - } else { - // create a white buffer for the surface - struct wl_buffer* buffer = nullptr; - buffer = createShmBuffer(data->w, data->h, nullptr, false); - if (!buffer) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - wlSurfaceAttach(surface, buffer, 0, 0); - wlSurfaceDamageBuffer(surface, 0, 0, data->w, data->h); - wlSurfaceCommit(surface); - data->buffer = buffer; - } - - struct wl_region* region = wlCompositorCreateRegion(s_Wl.compositor); - if (region) { - wlRegionAdd(region, 0, 0, data->w, data->h); - wlSurfaceSetOpaqueRegion(surface, region); - wlRegionDestroy(region); - wlSurfaceCommit(surface); - } - - s_Wl.displayRoundtrip(s_Wl.display); - if (!s_Wl.decorationManager && s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalDispatchMode mode = PAL_DISPATCH_NONE; - PalEventType type = PAL_EVENT_WINDOW_DECORATION_MODE; - mode = palGetEventDispatchMode(driver, type); - - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = PAL_DECORATION_MODE_CLIENT_SIDE; - event.data2 = palPackPointer(data->window); - palPushEvent(driver, &event); - } - } - - // Since wayland does not have a way to set unique data - // to a surface without taking control from users - // we might implement a simple hash map to do that - // but at the moment a linear search is fine - // FIXME: Implement a window hash map - - data->skipState = false; - data->skipConfigure = false; - *outWindow = data->window; - return PAL_RESULT_SUCCESS; -} - -void wlDestroyWindow(PalWindow* window) -{ - WindowData* data = findWindowData(window); - if (!data || (data && data->isAttached)) { - return; - } - - if (data->decoration) { - zxdgToplevelDecorationV1Destroy(data->decoration); - } - - if (data->eglWindow) { - s_Wl.eglWindowDestroy(data->eglWindow); - } else { - wlBufferDestroy(data->buffer); - } - - xdgToplevelDestroy(data->xdgToplevel); - xdgSurfaceDestroy(data->xdgSurface); - wlSurfaceDestroy((struct wl_surface*)window); - data->used = false; -} - -PalResult wlMinimizeWindow(PalWindow* window) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - xdgToplevelSetMinimized(data->xdgToplevel); - return PAL_RESULT_SUCCESS; -} - -PalResult wlMaximizeWindow(PalWindow* window) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - xdgToplevelSetMaximized(data->xdgToplevel); - return PAL_RESULT_SUCCESS; -} - -PalResult wlRestoreWindow(PalWindow* window) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - // we can only restore from a maximized state - xdgToplevelUnsetMaximized(data->xdgToplevel); - return PAL_RESULT_SUCCESS; -} - -PalResult wlShowWindow(PalWindow* window) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlHideWindow(PalWindow* window) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlFlashWindow( - PalWindow* window, - const PalFlashInfo* info) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowMonitor( - PalWindow* window, - PalMonitor** outMonitor) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowTitle( - PalWindow* window, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlGetWindowState( - PalWindow* window, - PalWindowState* outState) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -bool wlIsWindowVisible(PalWindow* window) -{ - return false; -} - -PalWindow* wlGetFocusWindow() -{ - // Wayland does not let client query focused window - return nullptr; -} - -PalWindowHandleInfo wlGetWindowHandleInfo(PalWindow* window) -{ - PalWindowHandleInfo info; - info.nativeDisplay = (void*)s_Wl.display; - info.nativeWindow = (void*)window; - return info; -} - -PalWindowHandleInfoEx wlGetWindowHandleInfoEx(PalWindow* window) -{ - PalWindowHandleInfoEx info = {0}; - WindowData* data = findWindowData(window); - if (data) { - info.nativeDisplay = (void*)s_Wl.display; - info.nativeWindow = (void*)window; - info.nativeHandle1 = data->xdgSurface; - info.nativeHandle2 = data->xdgToplevel; - info.nativeHandle3 = data->eglWindow; - } - - return info; -} - -PalResult wlSetWindowOpacity( - PalWindow* window, - float opacity) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetWindowStyle( - PalWindow* window, - PalWindowStyle style) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetWindowTitle( - PalWindow* window, - const char* title) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - xdgToplevelSetTitle(data->xdgToplevel, title); - s_Wl.displayFlush(s_Wl.display); - return PAL_RESULT_SUCCESS; -} - -PalResult wlSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetWindowSize( - PalWindow* window, - Uint32 width, - Uint32 height) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - xdgToplevelSetMinSize(data->xdgToplevel, width, height); - xdgToplevelSetMaxSize(data->xdgToplevel, width, height); - wlSurfaceCommit((struct wl_surface*)window); - return PAL_RESULT_SUCCESS; -} - -PalResult wlSetFocusWindow(PalWindow* window) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -void wlDestroyIcon(PalIcon* icon) -{ - return; -} - -PalResult wlSetWindowIcon( - PalWindow* window, - PalIcon* icon) -{ - return PAL_RESULT_THREAD_FEATURE_NOT_SUPPORTED; -} - -PalResult wlCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor) -{ - WaylandCursor* cursor = nullptr; - cursor = palAllocate(s_Video.allocator, sizeof(WaylandCursor), 0); - if (!cursor) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); - if (!cursor->surface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - cursor->buffer = - createShmBuffer(info->width, info->height, info->pixels, true); - - if (!cursor->buffer) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); - wlSurfaceCommit(cursor->surface); - cursor->hotspotX = info->xHotspot; - cursor->hotspotY = info->yHotspot; - - *outCursor = (PalCursor*)cursor; - return PAL_RESULT_SUCCESS; -} - -PalResult wlCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor) -{ - const char* cursorType = nullptr; - switch (type) { - case PAL_CURSOR_ARROW: { - cursorType = "left_ptr"; - break; - } - - case PAL_CURSOR_HAND: { - cursorType = "hand1"; - break; - } - - case PAL_CURSOR_CROSS: { - cursorType = "crosshair"; - break; - } - - case PAL_CURSOR_IBEAM: { - cursorType = "text"; - break; - } - - case PAL_CURSOR_WAIT: { - cursorType = "wait"; - break; - } - } - - struct wl_cursor* wlCursor = nullptr; - wlCursor = s_Wl.cursorThemeGetCursor(s_Wl.cursorTheme, cursorType); - if (!wlCursor) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - WaylandCursor* cursor = nullptr; - cursor = palAllocate(s_Video.allocator, sizeof(WaylandCursor), 0); - if (!cursor) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); - if (!cursor->surface) { - palSetLastPlatformError(errno); - return PAL_RESULT_PLATFORM_FAILURE; - } - - cursor->buffer = s_Wl.cursorImageGetBuffer(wlCursor->images[0]); - wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); - wlSurfaceCommit(cursor->surface); - cursor->hotspotX = wlCursor->images[0]->hotspot_x; - cursor->hotspotY = wlCursor->images[0]->hotspot_y; - - *outCursor = (PalCursor*)cursor; - return PAL_RESULT_SUCCESS; -} - -void wlDestroyCursor(PalCursor* cursor) -{ - WaylandCursor* waylandCursor = (WaylandCursor*)cursor; - wlBufferDestroy(waylandCursor->buffer); - wlSurfaceDestroy(waylandCursor->surface); - palFree(s_Video.allocator, waylandCursor); -} - -void wlShowCursor(bool show) -{ - // not supported - return; -} - -PalResult wlClipCursor( - PalWindow* window, - bool clip) -{ - if (!(s_Video.features & PAL_VIDEO_FEATURE_CLIP_CURSOR)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult wlGetCursorPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetCursorPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlSetWindowCursor( - PalWindow* window, - PalCursor* cursor) -{ - WindowData* data = findWindowData(window); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - data->cursor = cursor; - return PAL_RESULT_SUCCESS; -} - -void* wlGetInstance() -{ - return (void*)s_Wl.display; -} - -PalResult wlAttachWindow( - void* windowHandle, - PalWindow** outWindow) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -PalResult wlDetachWindow( - PalWindow* window, - void** outWindowHandle) -{ - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; -} - -static Backend s_wlBackend = { - .shutdownVideo = wlShutdownVideo, - .updateVideo = wlUpdateVideo, - .setFBConfig = wlSetFBConfig, - .enumerateMonitors = wlEnumerateMonitors, - .getMonitorInfo = wlGetMonitorInfo, - .getPrimaryMonitor = wlGetPrimaryMonitor, - .enumerateMonitorModes = wlEnumerateMonitorModes, - .getCurrentMonitorMode = wlGetCurrentMonitorMode, - .setMonitorMode = wlSetMonitorMode, - .validateMonitorMode = wlValidateMonitorMode, - .setMonitorOrientation = wlSetMonitorOrientation, - - .createWindow = wlCreateWindow, - .destroyWindow = wlDestroyWindow, - .maximizeWindow = wlMaximizeWindow, - .minimizeWindow = wlMinimizeWindow, - .restoreWindow = wlRestoreWindow, - .showWindow = wlShowWindow, - .hideWindow = wlHideWindow, - .flashWindow = wlFlashWindow, - .getWindowStyle = wlGetWindowStyle, - .getWindowMonitor = wlGetWindowMonitor, - .getWindowTitle = wlGetWindowTitle, - .getWindowPos = wlGetWindowPos, - .getWindowSize = wlGetWindowSize, - .getWindowState = wlGetWindowState, - .isWindowVisible = wlIsWindowVisible, - .getFocusWindow = wlGetFocusWindow, - .getWindowHandleInfo = wlGetWindowHandleInfo, - .getWindowHandleInfoEx = wlGetWindowHandleInfoEx, - .setWindowOpacity = wlSetWindowOpacity, - .setWindowStyle = wlSetWindowStyle, - .setWindowTitle = wlSetWindowTitle, - .setWindowPos = wlSetWindowPos, - .setWindowSize = wlSetWindowSize, - .setFocusWindow = wlSetFocusWindow, - - .createIcon = wlCreateIcon, - .destroyIcon = wlDestroyIcon, - .setWindowIcon = wlSetWindowIcon, - - .createCursor = wlCreateCursor, - .createCursorFrom = wlCreateCursorFrom, - .destroyCursor = wlDestroyCursor, - .showCursor = wlShowCursor, - .clipCursor = wlClipCursor, - .getCursorPos = wlGetCursorPos, - .setCursorPos = wlSetCursorPos, - .setWindowCursor = wlSetWindowCursor, - - .attachWindow = wlAttachWindow, - .detachWindow = wlDetachWindow}; - -#endif // PAL_HAS_WAYLAND -#pragma endregion - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palInitVideo( - const PalAllocator* allocator, - PalEventDriver* eventDriver) -{ - if (s_Video.initialized) { - return PAL_RESULT_SUCCESS; - } - - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - // get backend type - bool x11 = true; - const char* session = getenv("XDG_SESSION_TYPE"); - if (session) { - if (strcmp(session, "wayland") == 0) { - x11 = false; - } - } - - s_Video.maxMonitorData = 16; // initial size - s_Video.maxWindowData = 32; // initial size - - s_Video.windowData = palAllocate( - s_Video.allocator, - sizeof(WindowData) * s_Video.maxWindowData, - 0); - - s_Video.monitorData = palAllocate( - s_Video.allocator, - sizeof(MonitorData) * s_Video.maxMonitorData, - 0); - - if (!s_Video.monitorData || !s_Video.windowData) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - s_Video.className = "PAL"; - if (x11) { -#if PAL_HAS_X11 - PalResult ret = xInitVideo(); - if (ret != PAL_RESULT_SUCCESS) { - return ret; - } - s_Video.backend = &s_XBackend; -#else - return PAL_RESULT_PLATFORM_FAILURE; -#endif // PAL_HAS_X11 - - } else { -#if PAL_HAS_WAYLAND - PalResult ret = wlInitVideo(); - if (ret != PAL_RESULT_SUCCESS) { - return ret; - } - s_Video.backend = &s_wlBackend; -#else - return PAL_RESULT_PLATFORM_FAILURE; -#endif // PAL_HAS_WAYLAND - } - - createScancodeTable(); - - // we load EGL as well - s_Egl.handle = dlopen("libEGL.so", RTLD_LAZY); - if (s_Egl.handle) { - eglGetProcAddressFn load = nullptr; - load = (eglGetProcAddressFn)dlsym(s_Egl.handle, "eglGetProcAddress"); - - s_Egl.eglInitialize = (eglInitializeFn)load("eglInitialize"); - s_Egl.eglTerminate = (eglTerminateFn)load("eglTerminate"); - s_Egl.eglGetDisplay = (eglGetDisplayFn)load("eglGetDisplay"); - s_Egl.eglChooseConfig = (eglChooseConfigFn)load("eglChooseConfig"); - s_Egl.eglGetError = (eglGetErrorFn)load("eglGetError"); - s_Egl.eglBindAPI = (eglBindAPIFn)load("eglBindAPI"); - s_Egl.eglGetConfigs = (eglGetConfigsFn)load("eglGetConfigs"); - - // clang-format off - s_Egl.eglGetConfigAttrib = (eglGetConfigAttribFn)load("eglGetConfigAttrib"); - // clang-format on - } - - s_Video.allocator = allocator; - s_Video.eventDriver = eventDriver; - s_Video.initialized = true; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palShutdownVideo() -{ - if (s_Video.initialized) { - s_Video.backend->shutdownVideo(); - palFree(s_Video.allocator, s_Video.windowData); - palFree(s_Video.allocator, s_Video.monitorData); - - if (s_Egl.handle) { - dlclose(s_Egl.handle); - } - - s_Video.platformInstance = nullptr; - s_Video.display = nullptr; - memset(&s_Keyboard, 0, sizeof(Keyboard)); - memset(&s_Mouse, 0, sizeof(Mouse)); - s_Video.initialized = false; - } -} - -void PAL_CALL palUpdateVideo() -{ - if (s_Video.initialized) { - s_Video.backend->updateVideo(); - } -} - -PalVideoFeatures PAL_CALL palGetVideoFeatures() -{ - if (!s_Video.initialized) { - return 0; - } - - return s_Video.features; -} - -PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx() -{ - if (!s_Video.initialized) { - return 0; - } - - return s_Video.features64; -} - -PalResult PAL_CALL palSetFBConfig( - const int index, - PalFBConfigBackend backend) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - // X11 and wayland can only used GLX and EGL - if (backend == PAL_CONFIG_BACKEND_WGL) { - return PAL_RESULT_INVALID_FBCONFIG_BACKEND; - } - - return s_Video.backend->setFBConfig(index, backend); -} - -PalResult PAL_CALL palEnumerateMonitors( - Int32* count, - PalMonitor** outMonitors) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!count) { - return PAL_RESULT_NULL_POINTER; - } - - if (*count == 0 && outMonitors) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - return s_Video.backend->enumerateMonitors(count, outMonitors); -} - -PalResult PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!outMonitor) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getPrimaryMonitor(outMonitor); -} - -PalResult PAL_CALL palGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getMonitorInfo(monitor, info); -} - -PalResult PAL_CALL palEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !count) { - return PAL_RESULT_NULL_POINTER; - } - - if (*count == 0 && modes) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - // clang-format off - - PalResult ret = s_Video.backend->enumerateMonitorModes(monitor, count, modes); - // clang-format on - - if (ret == PAL_RESULT_SUCCESS && modes) { - // sort the modes so that they are lowest to highest - qsort(modes, *count, sizeof(PalMonitorMode), compareModes); - } - - return ret; -} - -PalResult PAL_CALL palGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !mode) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getCurrentMonitorMode(monitor, mode); -} - -PalResult PAL_CALL palSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !mode) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setMonitorMode(monitor, mode); -} - -PalResult PAL_CALL palValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !mode) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->validateMonitorMode(monitor, mode); -} - -PalResult PAL_CALL palSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setMonitorOrientation(monitor, orientation); -} - -// ================================================== -// Window -// ================================================== - -PalResult PAL_CALL palCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outWindow) { - return PAL_RESULT_NULL_POINTER; - } - - if (info->style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - if (info->style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - return s_Video.backend->createWindow(info, outWindow); -} - -void PAL_CALL palDestroyWindow(PalWindow* window) -{ - if (s_Video.initialized && window) { - return s_Video.backend->destroyWindow(window); - } -} - -PalResult PAL_CALL palMinimizeWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->maximizeWindow(window); -} - -PalResult PAL_CALL palMaximizeWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->minimizeWindow(window); -} - -PalResult PAL_CALL palRestoreWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->restoreWindow(window); -} - -PalResult PAL_CALL palShowWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->showWindow(window); -} - -PalResult PAL_CALL palHideWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->hideWindow(window); -} - -PalResult PAL_CALL palFlashWindow( - PalWindow* window, - const PalFlashInfo* info) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !info) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->flashWindow(window, info); -} - -PalResult PAL_CALL palGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outStyle) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getWindowStyle(window, outStyle); -} - -PalResult PAL_CALL palGetWindowMonitor( - PalWindow* window, - PalMonitor** outMonitor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outMonitor) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getWindowMonitor(window, outMonitor); -} - -PalResult PAL_CALL palGetWindowTitle( - PalWindow* window, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outBuffer) { - return PAL_RESULT_NULL_POINTER; - } - - // clang-format off - return s_Video.backend->getWindowTitle( - window, - bufferSize, - outSize, - outBuffer); - // clang-format on -} - -PalResult PAL_CALL palGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getWindowPos(window, x, y); -} - -PalResult PAL_CALL palGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getWindowSize(window, width, height); -} - -PalResult PAL_CALL palGetWindowState( - PalWindow* window, - PalWindowState* outState) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outState) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getWindowState(window, outState); -} - -const bool* PAL_CALL palGetKeycodeState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Keyboard.keycodeState; -} - -const bool* PAL_CALL palGetScancodeState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Keyboard.scancodeState; -} - -const bool* PAL_CALL palGetMouseState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Mouse.state; -} - -void PAL_CALL palGetMouseDelta( - Int32* dx, - Int32* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = s_Mouse.dx; - } - - if (dy) { - *dy = s_Mouse.dy; - } -} - -void PAL_CALL palGetMouseWheelDelta( - Int32* dx, - Int32* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = s_Mouse.WheelX; - } - - if (dy) { - *dy = s_Mouse.WheelY; - } -} - -void PAL_CALL palGetRawMouseWheelDelta( - float* dx, - float* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = (float)s_Mouse.tmpScrollX; - } - - if (dy) { - *dy = (float)s_Mouse.tmpScrollY; - } -} - -bool PAL_CALL palIsWindowVisible(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->isWindowVisible(window); -} - -PalWindow* PAL_CALL palGetFocusWindow() -{ - if (!s_Video.initialized) { - return nullptr; - } - - return s_Video.backend->getFocusWindow(); -} - -PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window) -{ - if (s_Video.initialized) { - return s_Video.backend->getWindowHandleInfo(window); - } -} - -PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* w) -{ - if (s_Video.initialized) { - return s_Video.backend->getWindowHandleInfoEx(w); - } -} - -PalResult PAL_CALL palSetWindowOpacity( - PalWindow* window, - float opacity) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!(s_Video.features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW)) { - return PAL_RESULT_VIDEO_FEATURE_NOT_SUPPORTED; - } - - if (opacity < 0.0f) { - opacity = 0.0f; - } - - if (opacity > 1.0f) { - opacity = 1.0f; - } - - return s_Video.backend->setWindowOpacity(window, opacity); -} - -PalResult PAL_CALL palSetWindowStyle( - PalWindow* window, - PalWindowStyle style) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowStyle(window, style); -} - -PalResult PAL_CALL palSetWindowTitle( - PalWindow* window, - const char* title) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !title) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowTitle(window, title); -} - -PalResult PAL_CALL palSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowPos(window, x, y); -} - -PalResult PAL_CALL palSetWindowSize( - PalWindow* window, - Uint32 width, - Uint32 height) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowSize(window, width, height); -} - -PalResult PAL_CALL palSetFocusWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setFocusWindow(window); -} - -// ================================================== -// Icon -// ================================================== - -PalResult PAL_CALL palCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outIcon) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->createIcon(info, outIcon); -} - -void PAL_CALL palDestroyIcon(PalIcon* icon) -{ - if (s_Video.initialized && icon) { - s_Video.backend->destroyIcon(icon); - } -} - -PalResult PAL_CALL palSetWindowIcon( - PalWindow* window, - PalIcon* icon) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowIcon(window, icon); -} - -// ================================================== -// Cursor -// ================================================== - -PalResult PAL_CALL palCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outCursor) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->createCursor(info, outCursor); -} - -PalResult PAL_CALL palCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!outCursor) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->createCursorFrom(type, outCursor); -} - -void PAL_CALL palDestroyCursor(PalCursor* cursor) -{ - if (s_Video.initialized && cursor) { - s_Video.backend->destroyCursor(cursor); - } -} - -void PAL_CALL palShowCursor(bool show) -{ - if (s_Video.initialized) { - s_Video.backend->showCursor(show); - } -} - -PalResult PAL_CALL palClipCursor( - PalWindow* window, - bool clip) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->clipCursor(window, clip); -} - -PalResult PAL_CALL palGetCursorPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->getCursorPos(window, x, y); -} - -PalResult PAL_CALL palSetCursorPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setCursorPos(window, x, y); -} - -PalResult PAL_CALL palSetWindowCursor( - PalWindow* window, - PalCursor* cursor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->setWindowCursor(window, cursor); -} - -void* PAL_CALL palGetInstance() -{ - if (!s_Video.initialized) { - return nullptr; - } - - return s_Video.display; -} - -PalResult PAL_CALL palAttachWindow( - void* windowHandle, - PalWindow** outWindow) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!windowHandle) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->attachWindow(windowHandle, outWindow); -} - -PalResult PAL_CALL palDetachWindow( - PalWindow* window, - void** outWindowHandle) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - return s_Video.backend->detachWindow(window, outWindowHandle); -} - -void PAL_CALL palSetPreferredInstance(void* instance) -{ - if (!s_Video.initialized && instance) { - s_Video.platformInstance = instance; - } -} \ No newline at end of file diff --git a/src/video/pal_video_win32.c b/src/video/pal_video_win32.c deleted file mode 100644 index a95412f3..00000000 --- a/src/video/pal_video_win32.c +++ /dev/null @@ -1,3117 +0,0 @@ - -/** - -Copyright (C) 2025 Nicholas Agbo - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - - */ - -// ================================================== -// Includes -// ================================================== - -#include "pal/pal_video.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif // WIN32_LEAN_AND_MEAN - -#ifndef NOMINMAX -#define NOMINMAX -#endif // NOMINMAX - -// set unicode -#ifndef UNICODE -#define UNICODE -#endif // UNICODE - -#include -#include - -// ================================================== -// Typedefs, enums and structs -// ================================================== - -#define PAL_VIDEO_CLASS L"PALVideoClass" -#define PAL_VIDEO_PROP L"PalVideoData" -#define WIN32_DPI 0 -#define WIN32_DPI_AWARE 2 -#define MAX_MODE_COUNT 128 -#define NULL_ORIENTATION 5 -#define WINDOW_NAME_SIZE 256 -#define NULL_BUTTON_SERIAL 0xffffffffU - -typedef HRESULT(WINAPI* GetDpiForMonitorFn)( - HMONITOR, - Int32, - UINT*, - UINT*); - -typedef HRESULT(WINAPI* SetProcessAwarenessFn)(Int32); - -typedef HBITMAP(WINAPI* CreateDIBSectionFn)( - HDC, - const BITMAPINFO*, - UINT, - VOID**, - HANDLE, - DWORD); - -typedef HBITMAP(WINAPI* CreateBitmapFn)( - int, - int, - UINT, - UINT, - CONST VOID*); - -typedef BOOL(WINAPI* DeleteObjectFn)(HGDIOBJ); - -typedef int(WINAPI* DescribePixelFormatFn)( - HDC, - int, - UINT, - LPPIXELFORMATDESCRIPTOR); - -typedef BOOL(WINAPI* SetPixelFormatFn)( - HDC, - int, - CONST PIXELFORMATDESCRIPTOR*); - -typedef struct { - bool used; - bool isAttached; - PalWindowState state; - HCURSOR cursor; - LONG_PTR wndProc; -} WindowData; - -typedef struct { - bool initialized; - Int32 pixelFormat; - Int32 maxWindowData; - PalVideoFeatures features; - PalVideoFeatures64 features64; - const PalAllocator* allocator; - PalEventDriver* eventDriver; - HINSTANCE shcore; - GetDpiForMonitorFn getDpiForMonitor; - SetProcessAwarenessFn setProcessAwareness; - - HINSTANCE gdi; - CreateDIBSectionFn createDIBSection; - CreateBitmapFn createBitmap; - DeleteObjectFn deleteObject; - DescribePixelFormatFn describePixelFormat; - SetPixelFormatFn setPixelFormat; - - HINSTANCE instance; - HWND hiddenWindow; - HCURSOR defaultCursor; - WindowData* windowData; -} VideoWin32; - -typedef struct { - Int32 count; - Int32 maxCount; - PalMonitor** monitors; -} MonitorData; - -typedef struct { - bool pendingResize; - bool pendingMove; - bool pendingState; - Uint32 width; - Uint32 height; - Int32 x; - Int32 y; - PalWindowState state; - PalWindow* window; -} PendingEvent; - -typedef struct { - Int32 pendingHighSurrogate; - bool scancodeState[PAL_SCANCODE_MAX]; - bool keycodeState[PAL_KEYCODE_MAX]; - int scancodes[512]; - int keycodes[256]; -} Keyboard; - -typedef struct { - bool push; - Int32 dx; - Int32 dy; - Int32 WheelX; - Int32 WheelY; - bool state[PAL_MOUSE_BUTTON_MAX]; -} Mouse; - -static PendingEvent s_Event; -static VideoWin32 s_Video = {0}; - -static BYTE s_RawBuffer[4096] = {0}; -static Mouse s_Mouse = {0}; -static Keyboard s_Keyboard = {0}; - -// ================================================== -// Internal API -// ================================================== - -void palSetLastPlatformError(Uint32 e); - -LRESULT CALLBACK videoProc( - HWND hwnd, - UINT msg, - WPARAM wParam, - LPARAM lParam) -{ - // check if the window has been created - WindowData* data = (WindowData*)GetPropW(hwnd, PAL_VIDEO_PROP); - if (!data) { - // window has not been created yet - return DefWindowProcW(hwnd, msg, wParam, lParam); - } - - PalDispatchMode mode = PAL_DISPATCH_NONE; - switch (msg) { - case WM_CLOSE: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_CLOSE); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_CLOSE; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_SIZE: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_SIZE); - Uint32 width = (Uint32)LOWORD(lParam); - Uint32 height = (Uint32)HIWORD(lParam); - - if (mode == PAL_DISPATCH_CALLBACK) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_SIZE; - event.data = palPackUint32(width, height); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - - } else if (mode == PAL_DISPATCH_POLL) { - s_Event.pendingResize = true; - s_Event.width = width; - s_Event.height = height; - s_Event.window = (PalWindow*)hwnd; - } - - // trigger state event - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_STATE); - PalWindowState state = PAL_WINDOW_STATE_RESTORED; - if (mode == PAL_DISPATCH_NONE) { - return 0; - } - - switch (wParam) { - case SIZE_MINIMIZED: { - state = PAL_WINDOW_STATE_MINIMIZED; - break; - } - - case SIZE_MAXIMIZED: { - state = PAL_WINDOW_STATE_MAXIMIZED; - break; - } - } - - // if state has not changed, we discard the event - if (data->state == state) { - return 0; - } - - if (mode == PAL_DISPATCH_CALLBACK) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_STATE; - event.data = state; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - - } else if (mode == PAL_DISPATCH_POLL) { - s_Event.pendingState = true; - s_Event.state = state; - } - data->state = state; - } - - return 0; - } - - case WM_MOVE: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_MOVE); - Int32 x = GET_X_LPARAM(lParam); - Int32 y = GET_Y_LPARAM(lParam); - - if (mode == PAL_DISPATCH_CALLBACK) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_MOVE; - event.data = palPackInt32(x, y); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - - } else { - s_Event.pendingMove = true; - s_Event.x = x; - s_Event.y = y; - s_Event.window = (PalWindow*)hwnd; - } - } - - return 0; - } - - case WM_SHOWWINDOW: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_VISIBILITY; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = (bool)wParam; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_SETFOCUS: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_FOCUS); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_FOCUS; - event.data = true; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_KILLFOCUS: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_WINDOW_FOCUS); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_FOCUS; - event.data = false; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_ENTERSIZEMOVE: { - s_Mouse.push = false; - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_MODAL_BEGIN; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_EXITSIZEMOVE: { - s_Mouse.push = true; - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_WINDOW_MODAL_END; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_DPICHANGED: { - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MONITOR_DPI_CHANGED; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = HIWORD(wParam); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_DEVICECHANGE: { - // check if the monitors list has been changed - if (wParam != 0x0007) { - return 0; - } - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MONITOR_LIST_CHANGED; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_MOUSEHWHEEL: { - Int32 delta = GET_WHEEL_DELTA_WPARAM(wParam); - s_Mouse.WheelX = delta / WHEEL_DELTA; - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_MOUSE_WHEEL); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_MOUSE_WHEEL; - event.data = palPackInt32(delta, 0); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_MOUSEWHEEL: { - Int32 delta = GET_WHEEL_DELTA_WPARAM(wParam); - s_Mouse.WheelY = delta / WHEEL_DELTA; - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_MOUSE_WHEEL); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_MOUSE_WHEEL; - event.data = palPackInt32(0, delta); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_MOUSEMOVE: { - const Int32 x = GET_X_LPARAM(lParam); - const Int32 y = GET_Y_LPARAM(lParam); - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_MOUSE_MOVE); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_MOUSE_MOVE; - event.data = palPackInt32(x, y); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_INPUT: { - UINT size = sizeof(s_RawBuffer); - UINT result = GetRawInputData( - (HRAWINPUT)lParam, - RID_INPUT, - s_RawBuffer, - &size, - sizeof(RAWINPUTHEADER)); - - if (result == (UINT)-1) { - break; - } - - RAWINPUT* raw = (RAWINPUT*)s_RawBuffer; - RAWMOUSE* mouse = &raw->data.mouse; - // push only if we are not rresizing or moving with the mouse - if (s_Mouse.push && (mouse->lLastX || mouse->lLastY)) { - s_Mouse.dx += mouse->lLastX; - s_Mouse.dy += mouse->lLastY; - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - PalEventType type = PAL_EVENT_MOUSE_DELTA; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackInt32(s_Mouse.dx, s_Mouse.dy); - palPushEvent(driver, &event); - } - } - } - break; - } - - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_XBUTTONDOWN: - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: { - PalMouseButton button = PAL_MOUSE_BUTTON_UNKNOWN; - PalEventType type; - bool pressed = false; - - if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) { - button = PAL_MOUSE_BUTTON_LEFT; - - } else if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP) { - button = PAL_MOUSE_BUTTON_RIGHT; - - } else if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONUP) { - button = PAL_MOUSE_BUTTON_MIDDLE; - - } else if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONUP) { - // check which x buttton - WORD xButton = HIWORD(wParam); - if (xButton == XBUTTON1) { - button = PAL_MOUSE_BUTTON_X1; - - } else if (xButton == XBUTTON2) { - button = PAL_MOUSE_BUTTON_X2; - } - } - - // clang-format off - - // check if we pressed or released the button - if (msg == WM_LBUTTONDOWN || - msg == WM_RBUTTONDOWN || - msg == WM_MBUTTONDOWN || - msg == WM_XBUTTONDOWN) { - pressed = true; - type = PAL_EVENT_MOUSE_BUTTONDOWN; - - } else { - pressed = false; - type = PAL_EVENT_MOUSE_BUTTONUP; - } - - // clang-format on - - // set mouse capture - if (msg == WM_LBUTTONDOWN) { - SetCapture(hwnd); - - } else if (msg == WM_LBUTTONUP) { - ReleaseCapture(); - } - - s_Mouse.state[button] = pressed; - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(button, NULL_BUTTON_SERIAL); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - case WM_KEYUP: - case WM_SYSKEYUP: { - PalKeycode keycode = PAL_KEYCODE_UNKNOWN; - PalScancode scancode = PAL_SCANCODE_UNKNOWN; - PalEventType type; - Int32 win32Keycode; - Int32 win32Scancode; - bool pressed = false; - bool extended = false; - - pressed = (HIWORD(lParam) & KF_UP) ? false : true; - extended = (lParam >> 24) & 1; // we use this for special keys - win32Scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); - win32Keycode = (UINT)wParam; - - // spcecial scancode handling - if (!extended && win32Scancode == 0x045) { - scancode = PAL_SCANCODE_NUMLOCK; - - } else { - Uint16 index = win32Scancode | (extended << 8); - scancode = s_Keyboard.scancodes[index]; - } - - keycode = s_Keyboard.keycodes[win32Keycode]; - if (keycode == PAL_KEYCODE_UNKNOWN) { - // we didnt get any printable key - // we use the scancode - // Since PalKeycode and PalScancode have the same integers - // we can make a direct cast without a table - // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) - keycode = (PalKeycode)(Uint32)scancode; - } - - if (win32Keycode == VK_SNAPSHOT) { - // printscreen since the platform does not get us a keydown, we - // do that ourselves - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, PAL_EVENT_KEYDOWN); - keycode = PAL_KEYCODE_PRINTSCREEN; - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = PAL_EVENT_KEYDOWN; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - s_Keyboard.keycodeState[keycode] = true; - } - } - - // check before updating state - bool repeat = s_Keyboard.keycodeState[keycode]; - if (pressed) { - s_Keyboard.keycodeState[keycode] = true; - s_Keyboard.scancodeState[scancode] = true; - - type = PAL_EVENT_KEYDOWN; - if (repeat) { - type = PAL_EVENT_KEYREPEAT; - } - - } else { - s_Keyboard.keycodeState[keycode] = false; - s_Keyboard.scancodeState[scancode] = false; - type = PAL_EVENT_KEYUP; - } - - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode != PAL_DISPATCH_NONE) { - PalEvent event = {0}; - event.type = type; - event.data = palPackUint32(keycode, scancode); - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(driver, &event); - } - } - return 0; - } - - case WM_ERASEBKGND: { - return true; - } - - case WM_SETCURSOR: { - if (LOWORD(lParam) == HTCLIENT) { - if (data && data->cursor) { - SetCursor(data->cursor); - } else { - // no cursor, use default - SetCursor(s_Video.defaultCursor); - } - return TRUE; - } - break; - } - - case WM_CHAR: { - PalEventType type = PAL_EVENT_KEYCHAR; - Uint32 codepoint = 0; - if (s_Video.eventDriver) { - PalEventDriver* driver = s_Video.eventDriver; - mode = palGetEventDispatchMode(driver, type); - if (mode == PAL_DISPATCH_NONE) { - break; - } - } - // Most characters comes as two WM_CHAR messags or event - // we store the first one and combine with the second if we got any - Uint16 character = (Uint16)wParam; - if (character >= 0xD800 && character <= 0xDBFF) { - // high surrogate - s_Keyboard.pendingHighSurrogate = character; - } else if (character >= 0xDC00 && character <= 0xDFFF) { - if (s_Keyboard.pendingHighSurrogate) { - // low surrogate we combine both - Uint32 high = s_Keyboard.pendingHighSurrogate - 0xD800; - Uint32 low = character - 0xDC00; - codepoint = 0x10000 + ((high << 10) | low); - s_Keyboard.pendingHighSurrogate = 0; - } - - } else { - // normal character (A-Z) - codepoint = character; - } - - // push an event - PalEvent event = {0}; - event.type = type; - event.data = codepoint; - event.data2 = palPackPointer((PalWindow*)hwnd); - palPushEvent(s_Video.eventDriver, &event); - } - } - - return DefWindowProcW(hwnd, msg, wParam, lParam); -} - -BOOL CALLBACK enumMonitors( - HMONITOR monitor, - HDC hdc, - LPRECT lRect, - LPARAM lParam) -{ - MonitorData* data = (MonitorData*)lParam; - if (data->monitors) { - if (data->count < data->maxCount) { - data->monitors[data->count] = (PalMonitor*)monitor; - } - } - - data->count++; - return TRUE; -} - -static inline PalOrientation orientationFromWin32(DWORD orientation) -{ - switch (orientation) { - case DMDO_DEFAULT: - return PAL_ORIENTATION_LANDSCAPE; - - case DMDO_90: - return PAL_ORIENTATION_PORTRAIT; - - case DMDO_180: - return PAL_ORIENTATION_LANDSCAPE_FLIPPED; - - case DMDO_270: - return PAL_ORIENTATION_PORTRAIT_FLIPPED; - } - return PAL_ORIENTATION_LANDSCAPE; -} - -static inline DWORD orientationToin32(PalOrientation orientation) -{ - switch (orientation) { - case PAL_ORIENTATION_LANDSCAPE: - return DMDO_DEFAULT; - - case PAL_ORIENTATION_PORTRAIT: - return DMDO_90; - - case PAL_ORIENTATION_LANDSCAPE_FLIPPED: - return DMDO_180; - - case PAL_ORIENTATION_PORTRAIT_FLIPPED: - return DMDO_270; - } - return NULL_ORIENTATION; -} - -static inline PalResult setMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode, - bool test) -{ - if (!monitor || !mode) { - return PAL_RESULT_NULL_POINTER; - } - - MONITORINFOEXW mi = {0}; - mi.cbSize = sizeof(MONITORINFOEXW); - if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_MONITOR; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - DWORD flags = DM_PELSWIDTH | DM_PELSHEIGHT; - flags |= DM_DISPLAYFREQUENCY | DM_BITSPERPEL; - - DEVMODE devMode = {0}; - devMode.dmSize = sizeof(DEVMODE); - devMode.dmFields = flags; - - devMode.dmPelsWidth = mode->width; - devMode.dmPelsHeight = mode->height; - devMode.dmDisplayFrequency = mode->refreshRate; - devMode.dmBitsPerPel = mode->bpp; - - DWORD settingsFlag = CDS_FULLSCREEN; - if (test) { - settingsFlag = CDS_TEST; - } - - ULONG result = ChangeDisplaySettingsExW( - mi.szDevice, - &devMode, - NULL, - settingsFlag, - NULL); - - if (result == DISP_CHANGE_SUCCESSFUL) { - return PAL_RESULT_SUCCESS; - } else { - return PAL_RESULT_INVALID_MONITOR_MODE; - } -} - -static inline bool compareMonitorMode( - const PalMonitorMode* a, - const PalMonitorMode* b) -{ - - // clang-format off - - return a->bpp == b->bpp && - a->width == b->width && - a->height == b->height && - a->refreshRate == b->refreshRate; - - // clang-format on -} - -static inline void addMonitorMode( - PalMonitorMode* modes, - const PalMonitorMode* mode, - Int32* count) -{ - // check if we have a duplicate mode - for (Int32 i = 0; i < *count; i++) { - PalMonitorMode* oldMode = &modes[i]; - if (compareMonitorMode(oldMode, mode)) { - return; // discard it - } - } - - // new mode - modes[*count] = *mode; - *count += 1; -} - -static void createKeycodeTable() -{ - // Tis is for only printable and text input keys - - // Letters - s_Keyboard.keycodes['A'] = PAL_KEYCODE_A; - s_Keyboard.keycodes['B'] = PAL_KEYCODE_B; - s_Keyboard.keycodes['C'] = PAL_KEYCODE_C; - s_Keyboard.keycodes['D'] = PAL_KEYCODE_D; - s_Keyboard.keycodes['E'] = PAL_KEYCODE_E; - s_Keyboard.keycodes['F'] = PAL_KEYCODE_F; - s_Keyboard.keycodes['G'] = PAL_KEYCODE_G; - s_Keyboard.keycodes['H'] = PAL_KEYCODE_H; - s_Keyboard.keycodes['I'] = PAL_KEYCODE_I; - s_Keyboard.keycodes['J'] = PAL_KEYCODE_J; - s_Keyboard.keycodes['K'] = PAL_KEYCODE_K; - s_Keyboard.keycodes['L'] = PAL_KEYCODE_L; - s_Keyboard.keycodes['M'] = PAL_KEYCODE_M; - s_Keyboard.keycodes['N'] = PAL_KEYCODE_N; - s_Keyboard.keycodes['O'] = PAL_KEYCODE_O; - s_Keyboard.keycodes['P'] = PAL_KEYCODE_P; - s_Keyboard.keycodes['Q'] = PAL_KEYCODE_Q; - s_Keyboard.keycodes['R'] = PAL_KEYCODE_R; - s_Keyboard.keycodes['S'] = PAL_KEYCODE_S; - s_Keyboard.keycodes['T'] = PAL_KEYCODE_T; - s_Keyboard.keycodes['U'] = PAL_KEYCODE_U; - s_Keyboard.keycodes['V'] = PAL_KEYCODE_V; - s_Keyboard.keycodes['W'] = PAL_KEYCODE_W; - s_Keyboard.keycodes['X'] = PAL_KEYCODE_X; - s_Keyboard.keycodes['Y'] = PAL_KEYCODE_Y; - s_Keyboard.keycodes['Z'] = PAL_KEYCODE_Z; - - // Control - s_Keyboard.keycodes[VK_SPACE] = PAL_KEYCODE_SPACE; - - // Misc - s_Keyboard.keycodes[VK_OEM_7] = PAL_KEYCODE_APOSTROPHE; - s_Keyboard.keycodes[VK_OEM_5] = PAL_KEYCODE_BACKSLASH; - s_Keyboard.keycodes[VK_OEM_COMMA] = PAL_KEYCODE_COMMA; - s_Keyboard.keycodes[VK_OEM_PLUS] = PAL_KEYCODE_EQUAL; - s_Keyboard.keycodes[VK_OEM_3] = PAL_KEYCODE_GRAVEACCENT; - s_Keyboard.keycodes[VK_OEM_MINUS] = PAL_KEYCODE_SUBTRACT; - s_Keyboard.keycodes[VK_OEM_PERIOD] = PAL_KEYCODE_PERIOD; - s_Keyboard.keycodes[VK_OEM_1] = PAL_KEYCODE_SEMICOLON; - s_Keyboard.keycodes[VK_OEM_2] = PAL_KEYCODE_SLASH; - s_Keyboard.keycodes[VK_OEM_4] = PAL_KEYCODE_LBRACKET; - s_Keyboard.keycodes[VK_OEM_6] = PAL_KEYCODE_RBRACKET; -} - -static void createScancodeTable() -{ - // Scancodes are made from OR'ed (scancode | extended) - // Letters - s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; - s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; - s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; - s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; - s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; - s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; - s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; - s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; - s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; - s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; - s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; - s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; - s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; - s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; - s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; - s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; - s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; - s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; - s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; - s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; - s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; - s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; - s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; - s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; - s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; - s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; - - // Numbers (top row) - s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; - s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; - s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; - s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; - s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; - s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; - s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; - s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; - s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; - s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; - - // Function - s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; - s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; - s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; - s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; - s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; - s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; - s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; - s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; - s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; - s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; - s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; - s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; - - // Control - s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; - s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; - s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; - s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; - s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; - s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; - s_Keyboard.scancodes[0x145] = PAL_SCANCODE_NUMLOCK; - s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; - s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; - s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; - s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; - s_Keyboard.scancodes[0x11D] = PAL_SCANCODE_RCTRL; - s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; - s_Keyboard.scancodes[0x138] = PAL_SCANCODE_RALT; - - // Arrows - s_Keyboard.scancodes[0x14B] = PAL_SCANCODE_LEFT; - s_Keyboard.scancodes[0x14D] = PAL_SCANCODE_RIGHT; - s_Keyboard.scancodes[0x148] = PAL_SCANCODE_UP; - s_Keyboard.scancodes[0x150] = PAL_SCANCODE_DOWN; - - // Navigation - s_Keyboard.scancodes[0x152] = PAL_SCANCODE_INSERT; - s_Keyboard.scancodes[0x153] = PAL_SCANCODE_DELETE; - s_Keyboard.scancodes[0x147] = PAL_SCANCODE_HOME; - s_Keyboard.scancodes[0x14F] = PAL_SCANCODE_END; - s_Keyboard.scancodes[0x149] = PAL_SCANCODE_PAGEUP; - s_Keyboard.scancodes[0x151] = PAL_SCANCODE_PAGEDOWN; - - // Keypad - s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; - s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; - s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; - s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; - s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; - s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; - s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; - s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; - s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; - s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; - s_Keyboard.scancodes[0x11C] = PAL_SCANCODE_KP_ENTER; - s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; - s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; - s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; - s_Keyboard.scancodes[0x135] = PAL_SCANCODE_KP_DIVIDE; - s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; - s_Keyboard.scancodes[0x059] = PAL_SCANCODE_KP_EQUAL; - - // Misc - s_Keyboard.scancodes[0x137] = PAL_SCANCODE_PRINTSCREEN; - s_Keyboard.scancodes[0x146] = PAL_SCANCODE_PAUSE; - s_Keyboard.scancodes[0x045] = PAL_SCANCODE_PAUSE; - s_Keyboard.scancodes[0x15D] = PAL_SCANCODE_MENU; - s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; - s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; - s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; - s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; - s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; - s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; - s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; - s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; - s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; - s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; - s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; - s_Keyboard.scancodes[0x15B] = PAL_SCANCODE_LSUPER; - s_Keyboard.scancodes[0x15C] = PAL_SCANCODE_RSUPER; -} - -static WindowData* getFreeWindowData() -{ - for (int i = 0; i < s_Video.maxWindowData; ++i) { - if (!s_Video.windowData[i].used) { - s_Video.windowData[i].used = true; - return &s_Video.windowData[i]; - } - } - - // resize the data array - // It is rare for a user to create and manage - // 32 windows at the same time - WindowData* data = nullptr; - int count = s_Video.maxWindowData * 2; // double the size - int freeIndex = s_Video.maxWindowData + 1; - data = palAllocate(s_Video.allocator, sizeof(WindowData) * count, 0); - if (data) { - memcpy( - data, - s_Video.windowData, - s_Video.maxWindowData * sizeof(WindowData)); - - palFree(s_Video.allocator, s_Video.windowData); - s_Video.windowData = data; - s_Video.maxWindowData = count; - - s_Video.windowData[freeIndex].used = true; - return &s_Video.windowData[freeIndex]; - } - return nullptr; -} - -// ================================================== -// Public API -// ================================================== - -PalResult PAL_CALL palInitVideo( - const PalAllocator* allocator, - PalEventDriver* eventDriver) -{ - if (s_Video.initialized) { - return PAL_RESULT_SUCCESS; - } - - if (allocator && (!allocator->allocate || !allocator->free)) { - return PAL_RESULT_INVALID_ALLOCATOR; - } - - s_Video.maxWindowData = 32; - s_Video.windowData = palAllocate( - s_Video.allocator, - sizeof(WindowData) * s_Video.maxWindowData, - 0); - - // get the instance - if (!s_Video.instance) { - s_Video.instance = GetModuleHandleW(nullptr); - } - - // load default cursor - s_Video.defaultCursor = LoadCursorW(NULL, IDC_ARROW); - - // register class - WNDCLASSEXW wc = {0}; - wc.cbSize = sizeof(WNDCLASSEXW); - wc.hCursor = s_Video.defaultCursor; - wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); - wc.hIconSm = LoadIconW(NULL, IDI_APPLICATION); - wc.hInstance = s_Video.instance; - wc.lpfnWndProc = videoProc; - wc.lpszClassName = PAL_VIDEO_CLASS; - wc.style = CS_OWNDC; - - // since we check every input carefully, the only error we can get is access - // denied - if (!RegisterClassExW(&wc)) { - return PAL_RESULT_ACCESS_DENIED; - } - - // create hidden window - s_Video.hiddenWindow = CreateWindowExW( - 0, - PAL_VIDEO_CLASS, - L"HiddenWindow", - WS_OVERLAPPEDWINDOW, - 0, - 0, - 8, - 8, - nullptr, - nullptr, - s_Video.instance, - nullptr); - - if (!s_Video.hiddenWindow) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // set a flag to check if the window has been created - SetPropW(s_Video.hiddenWindow, PAL_VIDEO_PROP, &s_Event); - - // register raw input for mice to get delta - RAWINPUTDEVICE rid = {0}; - rid.dwFlags = RIDEV_INPUTSINK; - rid.hwndTarget = s_Video.hiddenWindow; - rid.usUsage = 0x02; - rid.usUsagePage = 0x01; - if (!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE))) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // create mapping table - createKeycodeTable(); - createScancodeTable(); - - // load shared libraries - // shcore - s_Video.shcore = LoadLibraryA("shcore.dll"); - if (s_Video.shcore) { - s_Video.getDpiForMonitor = (GetDpiForMonitorFn)GetProcAddress( - s_Video.shcore, - "GetDpiForMonitor"); - - s_Video.setProcessAwareness = (SetProcessAwarenessFn)GetProcAddress( - s_Video.shcore, - "SetProcessDpiAwareness"); - } - - // clang-format off - - // gdi functios - s_Video.gdi = LoadLibraryA("gdi32.dll"); - if (s_Video.gdi) { - s_Video.createDIBSection = (CreateDIBSectionFn)GetProcAddress( - s_Video.gdi, - "CreateDIBSection"); - - s_Video.createBitmap = (CreateBitmapFn)GetProcAddress( - s_Video.gdi, - "CreateBitmap"); - - s_Video.deleteObject = (DeleteObjectFn)GetProcAddress( - s_Video.gdi, - "DeleteObject"); - - s_Video.describePixelFormat = (DescribePixelFormatFn)GetProcAddress( - s_Video.gdi, - "DescribePixelFormat"); - - s_Video.setPixelFormat = (SetPixelFormatFn)GetProcAddress( - s_Video.gdi, - "SetPixelFormat"); - } - - // clang-format on - - // set features - s_Video.features |= PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION; - s_Video.features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; - s_Video.features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; - s_Video.features |= PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW; - s_Video.features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; - s_Video.features |= PAL_VIDEO_FEATURE_MONITOR_SET_MODE; - s_Video.features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; - s_Video.features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_SIZE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_POS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_POS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_TITLE; - s_Video.features |= PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX; - s_Video.features |= PAL_VIDEO_FEATURE_NO_MINIMIZEBOX; - s_Video.features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_STYLE; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_GET_STYLE; - s_Video.features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; - s_Video.features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; - s_Video.features |= PAL_VIDEO_FEATURE_WINDOW_SET_ICON; - - if (s_Video.getDpiForMonitor && s_Video.setProcessAwareness) { - s_Video.features |= PAL_VIDEO_FEATURE_HIGH_DPI; - s_Video.features64 |= PAL_VIDEO_FEATURE64_HIGH_DPI; - s_Video.setProcessAwareness(WIN32_DPI_AWARE); - } - - // extended features - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION; - s_Video.features64 |= PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW; - s_Video.features64 |= PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW; - s_Video.features64 |= PAL_VIDEO_FEATURE64_TOOL_WINDOW; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_SET_MODE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_MODE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MULTI_MONITORS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_POS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_POS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STATE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_STATE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX; - s_Video.features64 |= PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX; - s_Video.features64 |= PAL_VIDEO_FEATURE64_CLIP_CURSOR; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_POS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_GET_POS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_ICON; - - s_Video.features64 |= PAL_VIDEO_FEATURE64_TOPMOST_WINDOW; - s_Video.features64 |= PAL_VIDEO_FEATURE64_DECORATED_WINDOW; - s_Video.features64 |= PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY; - s_Video.features64 |= PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS; - s_Video.features64 |= PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE; - s_Video.features64 |= PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR; - - s_Video.initialized = true; - s_Video.allocator = allocator; - s_Video.eventDriver = eventDriver; - s_Video.pixelFormat = 0; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palShutdownVideo() -{ - if (!s_Video.initialized) { - return; - } - - if (s_Video.shcore) { - FreeLibrary(s_Video.shcore); - } - - FreeLibrary(s_Video.gdi); - DestroyWindow(s_Video.hiddenWindow); - UnregisterClassW(PAL_VIDEO_CLASS, s_Video.instance); - palFree(s_Video.allocator, s_Video.windowData); - - memset(&s_Video, 0, sizeof(VideoWin32)); - memset(&s_Keyboard, 0, sizeof(Keyboard)); - memset(&s_Mouse, 0, sizeof(Mouse)); - - s_Video.windowData = nullptr; - s_Video.initialized = false; -} - -void PAL_CALL palUpdateVideo() -{ - if (!s_Video.initialized) { - return; - } - - s_Mouse.dx = 0; - s_Mouse.dy = 0; - - MSG msg; - while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { - TranslateMessage(&msg); - DispatchMessageA(&msg); - } - - // push pending move and reszie events - if (s_Event.pendingResize) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_SIZE; - event.data = palPackUint32(s_Event.width, s_Event.height); - event.data2 = palPackPointer(s_Event.window); - palPushEvent(s_Video.eventDriver, &event); - - if (s_Event.pendingState) { - PalEvent event = {0}; - event.data = s_Event.state; - event.data2 = palPackPointer(s_Event.window); - event.type = PAL_EVENT_WINDOW_STATE; - palPushEvent(s_Video.eventDriver, &event); - s_Event.pendingState = false; - } - - s_Event.pendingResize = false; - - } else if (s_Event.pendingMove) { - PalEvent event = {0}; - event.type = PAL_EVENT_WINDOW_MOVE; - event.data = palPackInt32(s_Event.x, s_Event.y); - event.data2 = palPackPointer(s_Event.window); - palPushEvent(s_Video.eventDriver, &event); - s_Event.pendingMove = false; - } -} - -PalVideoFeatures PAL_CALL palGetVideoFeatures() -{ - if (!s_Video.initialized) { - return 0; - } - - return s_Video.features; -} - -PalVideoFeatures64 PAL_CALL palGetVideoFeaturesEx() -{ - if (!s_Video.initialized) { - return 0; - } - - return s_Video.features64; -} - -PalResult PAL_CALL palSetFBConfig( - const int index, - PalFBConfigBackend backend) -{ - // Win32 uses only WGL and WGL index starts from 1 - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (backend == PAL_CONFIG_BACKEND_EGL || - backend == PAL_CONFIG_BACKEND_GLES || - backend == PAL_CONFIG_BACKEND_GLX) { - return PAL_RESULT_INVALID_FBCONFIG_BACKEND; - } - - if (index >= 1) { - s_Video.pixelFormat = index; - return PAL_RESULT_SUCCESS; - } - return PAL_RESULT_INVALID_GL_FBCONFIG; -} - -// ================================================== -// Monitor -// ================================================== - -PalResult PAL_CALL palEnumerateMonitors( - Int32* count, - PalMonitor** outMonitors) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!count) { - return PAL_RESULT_NULL_POINTER; - } - - if (*count == 0 && outMonitors) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - MonitorData data; - data.count = 0; - data.monitors = outMonitors; - data.maxCount = outMonitors ? *count : 0; - EnumDisplayMonitors(nullptr, nullptr, enumMonitors, (LPARAM)&data); - - if (!outMonitors) { - *count = data.count; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetPrimaryMonitor(PalMonitor** outMonitor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!outMonitor) { - return PAL_RESULT_NULL_POINTER; - } - - HMONITOR monitor = nullptr; - monitor = MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); - if (!monitor) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - *outMonitor = (PalMonitor*)monitor; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetMonitorInfo( - PalMonitor* monitor, - PalMonitorInfo* info) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !info) { - return PAL_RESULT_NULL_POINTER; - } - - MONITORINFOEXW mi = {0}; - mi.cbSize = sizeof(MONITORINFOEXW); - if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_MONITOR; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - info->x = mi.rcMonitor.left; - info->y = mi.rcMonitor.top; - info->width = mi.rcMonitor.right - mi.rcMonitor.left; - info->height = mi.rcMonitor.bottom - mi.rcWork.top; - - // get name - WideCharToMultiByte( - CP_UTF8, - 0, - mi.szDevice, - -1, - info->name, - 32, - NULL, - NULL); - - DEVMODE devMode = {0}; - devMode.dmSize = sizeof(DEVMODE); - EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); - info->refreshRate = devMode.dmDisplayFrequency; - info->orientation = orientationFromWin32(devMode.dmDisplayOrientation); - - // get dpi scale - UINT dpiX, dpiY; - if (s_Video.getDpiForMonitor) { - s_Video.getDpiForMonitor((HMONITOR)monitor, WIN32_DPI, &dpiX, &dpiY); - - } else { - dpiX = 96; - } - info->dpi = dpiX; - - // check for primary monitor - HMONITOR primary = nullptr; - primary = MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); - - if (!primary) { - info->primary = false; - } - - if (primary == (HMONITOR)monitor) { - info->primary = true; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palEnumerateMonitorModes( - PalMonitor* monitor, - Int32* count, - PalMonitorMode* modes) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !count) { - return PAL_RESULT_NULL_POINTER; - } - - if (*count == 0 && modes) { - return PAL_RESULT_INSUFFICIENT_BUFFER; - } - - Int32 modeCount = 0; - Int32 maxModes = 0; - PalMonitorMode* monitorModes = nullptr; - - MONITORINFOEXW mi = {0}; - mi.cbSize = sizeof(MONITORINFOEXW); - if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_MONITOR; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - if (!modes) { - // allocate and store tmp monitor modesand check for the interested - // fields. - monitorModes = palAllocate( - s_Video.allocator, - sizeof(PalMonitorMode) * MAX_MODE_COUNT, - 0); - - if (!monitorModes) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - memset(monitorModes, 0, sizeof(PalMonitorMode) * MAX_MODE_COUNT); - maxModes = MAX_MODE_COUNT; - - } else { - monitorModes = modes; - maxModes = *count; - } - - DEVMODEW dm = {0}; - dm.dmSize = sizeof(DEVMODE); - for (Int32 i = 0; EnumDisplaySettingsW(mi.szDevice, i, &dm); i++) { - // Pal support up to 128 modes - if (modeCount > maxModes) { - break; - } - - PalMonitorMode* mode = &monitorModes[modeCount]; - mode->refreshRate = dm.dmDisplayFrequency; - mode->width = dm.dmPelsWidth; - mode->height = dm.dmPelsHeight; - mode->bpp = dm.dmBitsPerPel; - addMonitorMode(monitorModes, mode, &modeCount); - } - - if (!modes) { - *count = modeCount; - palFree(s_Video.allocator, monitorModes); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetCurrentMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor || !mode) { - return PAL_RESULT_NULL_POINTER; - } - - MONITORINFOEXW mi = {0}; - mi.cbSize = sizeof(MONITORINFOEXW); - if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_MONITOR; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - DEVMODE devMode = {0}; - devMode.dmSize = sizeof(DEVMODE); - EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); - mode->width = devMode.dmPelsWidth; - mode->height = devMode.dmPelsHeight; - mode->refreshRate = devMode.dmDisplayFrequency; - mode->bpp = devMode.dmBitsPerPel; - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - return setMonitorMode(monitor, mode, false); -} - -PalResult PAL_CALL palValidateMonitorMode( - PalMonitor* monitor, - PalMonitorMode* mode) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - return setMonitorMode(monitor, mode, true); -} - -PalResult PAL_CALL palSetMonitorOrientation( - PalMonitor* monitor, - PalOrientation orientation) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!monitor) { - return PAL_RESULT_NULL_POINTER; - } - - DWORD win32Orientation = orientationToin32(orientation); - if (orientation == NULL_ORIENTATION) { - return PAL_RESULT_INVALID_ORIENTATION; - } - - MONITORINFOEXW mi = {0}; - mi.cbSize = sizeof(MONITORINFOEXW); - if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_MONITOR; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - DEVMODE devMode = {0}; - devMode.dmSize = sizeof(DEVMODE); - EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); - DWORD monitorOrientation = devMode.dmDisplayOrientation; - - // clang-format off - - // only swap size if switching between landscape and portrait - bool isMonitorLandscape = (monitorOrientation == DMDO_DEFAULT || - monitorOrientation == DMDO_180); - - bool isLandscape = (win32Orientation == DMDO_DEFAULT || - win32Orientation == DMDO_180); - - if (isMonitorLandscape != isLandscape) { - DWORD tmp = devMode.dmPelsWidth; - devMode.dmPelsWidth = devMode.dmPelsHeight; - devMode.dmPelsHeight = tmp; - } - - devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYORIENTATION; - devMode.dmDisplayOrientation = win32Orientation; - - ULONG result = ChangeDisplaySettingsExW( - mi.szDevice, - &devMode, - NULL, - CDS_RESET, - NULL); - - // clang-format on - - if (result == DISP_CHANGE_SUCCESSFUL) { - return PAL_RESULT_SUCCESS; - - } else { - return PAL_RESULT_INVALID_ORIENTATION; - } -} - -// ================================================== -// Window -// ================================================== - -PalResult PAL_CALL palCreateWindow( - const PalWindowCreateInfo* info, - PalWindow** outWindow) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outWindow) { - return PAL_RESULT_NULL_POINTER; - } - - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - HWND handle = nullptr; - PalMonitor* monitor = nullptr; - PalMonitorInfo monitorInfo; - - Uint32 style = WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED; - Uint32 exStyle = 0; - - // no minimize box - if (!(info->style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX)) { - style |= WS_MINIMIZEBOX; - } - - // no maximize box - if (!(info->style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX)) { - style |= WS_MAXIMIZEBOX; - } - - // resizable window - if (info->style & PAL_WINDOW_STYLE_RESIZABLE) { - style |= WS_THICKFRAME; - - } else { - // not resizable. We remove the maximizebox even if user requested - style &= ~WS_MAXIMIZEBOX; - } - - // transparent window - if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { - exStyle |= WS_EX_LAYERED; - } - - // tool window - if (info->style & PAL_WINDOW_STYLE_TOOL) { - exStyle |= WS_EX_TOOLWINDOW; - } - - // topmost window - if (info->style & PAL_WINDOW_STYLE_TOPMOST) { - exStyle |= WS_EX_TOPMOST; - } - - // get monitor - if (info->monitor) { - monitor = info->monitor; - - } else { - // get primary monitor - monitor = (PalMonitor*)MonitorFromPoint( - (POINT){0, 0}, - MONITOR_DEFAULTTOPRIMARY); - - if (!monitor) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - // get monitor info - PalResult result = palGetMonitorInfo(monitor, &monitorInfo); - if (result != PAL_RESULT_SUCCESS) { - return result; - } - - // compose position. - Int32 x, y = 0; - // the position and size must be scaled with the dpi before this call - if (info->center) { - x = monitorInfo.x + (monitorInfo.width - info->width) / 2; - y = monitorInfo.y + (monitorInfo.height - info->height) / 2; - - } else { - // we set 100 for each axix - x = monitorInfo.x + 100; - y = monitorInfo.y + 100; - } - - // adjust the window size - RECT rect = {0, 0, 0, 0}; - rect.right = info->width; - rect.bottom = info->height; - AdjustWindowRectEx(&rect, style, 0, exStyle); - - wchar_t buffer[WINDOW_NAME_SIZE]; - MultiByteToWideChar(CP_UTF8, 0, info->title, -1, buffer, 256); - - // create the window - handle = CreateWindowExW( - exStyle, - PAL_VIDEO_CLASS, - buffer, - style, - x, - y, - rect.right - rect.left, - rect.bottom - rect.top, - nullptr, - nullptr, - s_Video.instance, - nullptr); - - if (!handle) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - // set the pixel format is set - if (s_Video.pixelFormat) { - HDC hdc = GetDC(handle); - // since we have the pixel format already - // we ask the OS (platform) to fill the pfd struct for us from that - // index - PIXELFORMATDESCRIPTOR pfd; - if (!s_Video.describePixelFormat( - hdc, - s_Video.pixelFormat, - sizeof(PIXELFORMATDESCRIPTOR), - &pfd)) { - return PAL_RESULT_INVALID_GL_FBCONFIG; - } - - s_Video.setPixelFormat(hdc, s_Video.pixelFormat, &pfd); - ReleaseDC(handle, hdc); - } - - // show, maximize and minimize - Int32 showFlag = SW_HIDE; - // maximize - if (info->maximized) { - showFlag = SW_MAXIMIZE; - data->state = PAL_WINDOW_STATE_MAXIMIZED; - } - - // minimized - if (info->minimized) { - showFlag = SW_MINIMIZE; - data->state = PAL_WINDOW_STATE_MINIMIZED; - } - - // shown - if (info->show) { - if (showFlag == SW_HIDE) { - // change only if maximize and minimize are not set - showFlag = SW_SHOW; - data->state = PAL_WINDOW_STATE_RESTORED; - } - } - - ShowWindow(handle, showFlag); - UpdateWindow(handle); - - if (info->style & PAL_WINDOW_STYLE_BORDERLESS) { - // revert changes - SetWindowLongPtrW(handle, GWL_STYLE, WS_POPUP); - SetWindowLongPtrW(handle, GWL_EXSTYLE, WS_EX_APPWINDOW); - - // force a frame update - SetWindowPos( - handle, - nullptr, - 0, - 0, - 0, - 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); - } - - data->isAttached = false; - data->cursor = nullptr; - data->wndProc = (LONG_PTR)videoProc; - SetPropW(handle, PAL_VIDEO_PROP, data); - *outWindow = (PalWindow*)handle; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyWindow(PalWindow* window) -{ - if (s_Video.initialized && window) { - WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); - // destroy only PAL created window - if (data->isAttached) { - return; - } - - DestroyWindow((HWND)window); - data->used = false; - } -} - -PalResult PAL_CALL palMinimizeWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!ShowWindow((HWND)window, SW_MINIMIZE)) { - // technically, this will fail if the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palMaximizeWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!ShowWindow((HWND)window, SW_MAXIMIZE)) { - // technically, this will fail if the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palRestoreWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!ShowWindow((HWND)window, SW_RESTORE)) { - // technically, this will fail if the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palShowWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!ShowWindow((HWND)window, SW_SHOW)) { - // technically, this will fail if the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palHideWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!ShowWindow((HWND)window, SW_HIDE)) { - // technically, this will fail if the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palFlashWindow( - PalWindow* window, - const PalFlashInfo* info) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !info) { - return PAL_RESULT_NULL_POINTER; - } - - DWORD flags = 0; - if (info->flags == PAL_FLASH_STOP) { - flags = FLASHW_STOP; - - } else { - if (info->flags & PAL_FLASH_CAPTION) { - flags |= FLASHW_CAPTION; - } - if (info->flags & PAL_FLASH_TRAY) { - flags |= FLASHW_TRAY; - flags |= FLASHW_TIMERNOFG; - } - } - - FLASHWINFO flashInfo = {0}; - flashInfo.cbSize = sizeof(FLASHWINFO); - flashInfo.dwFlags = flags; - flashInfo.dwTimeout = info->interval; - flashInfo.hwnd = (HWND)window; - flashInfo.uCount = info->count; - - bool success = FlashWindowEx(&flashInfo); - if (!success) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowStyle( - PalWindow* window, - PalWindowStyle* outStyle) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outStyle) { - return PAL_RESULT_NULL_POINTER; - } - - PalWindowStyle windowStyle = 0; - DWORD style = (DWORD)GetWindowLongPtrW((HWND)window, GWL_STYLE); - DWORD exStyle = (DWORD)GetWindowLongPtrW((HWND)window, GWL_EXSTYLE); - - if (!style) { - // since there is no window without styles - // we assume if we dont get any, the window handle is invalid - return PAL_RESULT_INVALID_WINDOW; - } - - // check if we can resize - if (style & WS_THICKFRAME) { - windowStyle |= PAL_WINDOW_STYLE_RESIZABLE; - } - - // check if we are transparent - if (exStyle & WS_EX_LAYERED) { - windowStyle |= PAL_WINDOW_STYLE_TRANSPARENT; - } - - // check if we are a topmost window - if (exStyle & WS_EX_TOPMOST) { - windowStyle |= PAL_WINDOW_STYLE_TOPMOST; - } - - // check if we have a minimize box - if (!(style & WS_MINIMIZEBOX)) { - windowStyle |= PAL_WINDOW_STYLE_NO_MINIMIZEBOX; - } - - // check if we have a maximize box - if (!(style & WS_MAXIMIZEBOX)) { - windowStyle |= PAL_WINDOW_STYLE_NO_MAXIMIZEBOX; - } - - // check if its a tool window - if (exStyle & WS_EX_TOOLWINDOW) { - windowStyle |= PAL_WINDOW_STYLE_TOOL; - } - - // we check borderless last since it will overwrite other styles - if (style & WS_POPUP) { - windowStyle |= PAL_WINDOW_STYLE_BORDERLESS; - - // we remove minimize and maximize box if set - windowStyle &= ~PAL_WINDOW_STYLE_NO_MINIMIZEBOX; - windowStyle &= ~PAL_WINDOW_STYLE_NO_MAXIMIZEBOX; - } - - *outStyle = windowStyle; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowMonitor( - PalWindow* window, - PalMonitor** outMonitor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outMonitor) { - return PAL_RESULT_NULL_POINTER; - } - - HMONITOR monitor = nullptr; - monitor = MonitorFromWindow((HWND)window, MONITOR_DEFAULTTONEAREST); - if (!monitor) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - *outMonitor = (PalMonitor*)monitor; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowTitle( - PalWindow* window, - Uint64 bufferSize, - Uint64* outSize, - char* outBuffer) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outBuffer) { - return PAL_RESULT_NULL_POINTER; - } - - wchar_t buffer[WINDOW_NAME_SIZE]; - if (GetWindowTextW((HWND)window, buffer, WINDOW_NAME_SIZE) == 0) { - return PAL_RESULT_INVALID_WINDOW; - } - - int len = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, nullptr, 0, 0, 0); - if (outSize) { - *outSize = len - 1; - } - - // see if user provided a buffer and write to it - if (outBuffer && bufferSize > 0) { - int write = (int)bufferSize - 1; - WideCharToMultiByte(CP_UTF8, 0, buffer, -1, outBuffer, write + 1, 0, 0); - outBuffer[write < len - 1 ? write : len - 1] = '\0'; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - RECT rect; - if (!GetWindowRect((HWND)window, &rect)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (x) { - *x = rect.left; - } - - if (y) { - *y = rect.top; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowSize( - PalWindow* window, - Uint32* width, - Uint32* height) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - RECT rect; - if (!GetWindowRect((HWND)window, &rect)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (width) { - *width = rect.right - rect.left; - } - - if (height) { - *height = rect.bottom - rect.top; - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetWindowState( - PalWindow* window, - PalWindowState* outState) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !outState) { - return PAL_RESULT_NULL_POINTER; - } - - WINDOWPLACEMENT wp = {0}; - if (!GetWindowPlacement((HWND)window, &wp)) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (wp.showCmd == SW_MINIMIZE) { - *outState = PAL_WINDOW_STATE_MINIMIZED; - - } else if (wp.showCmd == SW_MAXIMIZE) { - *outState = PAL_WINDOW_STATE_MAXIMIZED; - - } else if (wp.showCmd == SW_RESTORE || wp.showCmd == SW_NORMAL) { - *outState = PAL_WINDOW_STATE_RESTORED; - } - - return PAL_RESULT_SUCCESS; -} - -const bool* PAL_CALL palGetKeycodeState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Keyboard.keycodeState; -} - -const bool* PAL_CALL palGetScancodeState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Keyboard.scancodeState; -} - -const bool* PAL_CALL palGetMouseState() -{ - if (!s_Video.initialized) { - return nullptr; - } - return s_Mouse.state; -} - -void PAL_CALL palGetMouseDelta( - Int32* dx, - Int32* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = s_Mouse.dx; - } - - if (dy) { - *dy = s_Mouse.dy; - } -} - -void PAL_CALL palGetMouseWheelDelta( - Int32* dx, - Int32* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = s_Mouse.WheelX; - } - - if (dy) { - *dy = s_Mouse.WheelY; - } -} - -void PAL_CALL palGetRawMouseWheelDelta( - float* dx, - float* dy) -{ - if (!s_Video.initialized) { - return; - } - - if (dx) { - *dx = (float)s_Mouse.WheelX; - } - - if (dy) { - *dy = (float)s_Mouse.WheelY; - } -} - -bool PAL_CALL palIsWindowVisible(PalWindow* window) -{ - if (!s_Video.initialized) { - return false; - } - - if (!window) { - return false; - } - - return IsWindowVisible((HWND)window); -} - -PalWindow* PAL_CALL palGetFocusWindow() -{ - if (!s_Video.initialized) { - return nullptr; - } - return (PalWindow*)GetFocus(); -} - -PalWindowHandleInfo PAL_CALL palGetWindowHandleInfo(PalWindow* window) -{ - PalWindowHandleInfo handle = {0}; - if (s_Video.initialized && window) { - handle.nativeDisplay = nullptr; - handle.nativeWindow = (void*)window; - } - return handle; -} - -PalWindowHandleInfoEx PAL_CALL palGetWindowHandleInfoEx(PalWindow* window) -{ - PalWindowHandleInfoEx handle = {0}; - if (s_Video.initialized && window) { - handle.nativeDisplay = nullptr; - handle.nativeWindow = (void*)window; - } - return handle; -} - -PalResult PAL_CALL palSetWindowOpacity( - PalWindow* window, - float opacity) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (opacity < 0.0f) { - opacity = 0.0f; - } - - if (opacity > 1.0f) { - opacity = 1.0f; - } - - bool ret = SetLayeredWindowAttributes( - (HWND)window, - 0, - (BYTE)(opacity * 255), - LWA_ALPHA); - - if (!ret) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else if (error == ERROR_INVALID_PARAMETER) { - return PAL_RESULT_INVALID_ARGUMENT; - - } else { - // FIXME: check for child windows - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetWindowStyle( - PalWindow* window, - PalWindowStyle style) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - // convert our style to win32 styles and exStyles - // all windows have this styles - DWORD win32Style = WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED; - DWORD exStyle = 0; - - // check for resizing - if (style & PAL_WINDOW_STYLE_RESIZABLE) { - win32Style |= WS_THICKFRAME; - } - - // check for transparent window - if (style & PAL_WINDOW_STYLE_TRANSPARENT) { - exStyle |= WS_EX_LAYERED; - } - - // check for topmost window - if (style & PAL_WINDOW_STYLE_TOPMOST) { - exStyle |= WS_EX_TOPMOST; - } - - // check for no minimize box - if (style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX) { - win32Style &= ~WS_MINIMIZEBOX; - } - - // check for maximize box - if (style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX) { - win32Style &= ~WS_MAXIMIZEBOX; - } - - // check for tool window - if (style & PAL_WINDOW_STYLE_TOOL) { - exStyle |= WS_EX_TOOLWINDOW; - } - - // check for borderless window - if (style & PAL_WINDOW_STYLE_BORDERLESS) { - // revert the styles - win32Style = WS_POPUP; - exStyle = WS_EX_APPWINDOW; - } - - HWND hwnd = (HWND)window; - SetWindowLongPtrW(hwnd, GWL_STYLE, win32Style); - SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle); - - // force a frame update - bool success = SetWindowPos( - hwnd, - nullptr, - 0, - 0, - 0, - 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); - - if (success) { - return PAL_RESULT_SUCCESS; - - } else { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } -} - -PalResult PAL_CALL palSetWindowTitle( - PalWindow* window, - const char* title) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window || !title) { - return PAL_RESULT_NULL_POINTER; - } - - wchar_t buffer[WINDOW_NAME_SIZE]; - MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 256); - - if (!SetWindowTextW((HWND)window, buffer)) { - return PAL_RESULT_INVALID_WINDOW; - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetWindowPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - bool success = SetWindowPos( - (HWND)window, - nullptr, - x, - y, - 0, - 0, - SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); - - if (!success) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetWindowSize( - PalWindow* window, - Uint32 width, - Uint32 height) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - bool success = SetWindowPos( - (HWND)window, - HWND_TOP, - 0, - 0, - width, - height, - SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER); - - if (!success) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else if (error == ERROR_INVALID_PARAMETER) { - return PAL_RESULT_INVALID_ARGUMENT; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetFocusWindow(PalWindow* window) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!SetActiveWindow((HWND)window)) { - DWORD error = GetLastError(); - if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else if (error == ERROR_ACCESS_DENIED) { - return PAL_RESULT_ACCESS_DENIED; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - } - return PAL_RESULT_SUCCESS; -} - -// ================================================== -// Icon -// ================================================== - -PalResult PAL_CALL palCreateIcon( - const PalIconCreateInfo* info, - PalIcon** outIcon) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outIcon) { - return PAL_RESULT_NULL_POINTER; - } - - // describe the icon pixels - BITMAPV5HEADER bitInfo = {0}; - bitInfo.bV5Size = sizeof(BITMAPV5HEADER); - bitInfo.bV5Width = info->width; - bitInfo.bV5Height = -(Int32)info->height; // this is topdown by default - - // default parameters - bitInfo.bV5Planes = 1; - bitInfo.bV5BitCount = 32; // PAL supports 32 bits - bitInfo.bV5Compression = BI_BITFIELDS; - bitInfo.bV5RedMask = 0x00FF0000; - bitInfo.bV5GreenMask = 0x0000FF00; - bitInfo.bV5BlueMask = 0x000000FF; - bitInfo.bV5AlphaMask = 0xFF000000; - - HDC hdc = GetDC(nullptr); - void* dibPixels = nullptr; - - // create dib section - HBITMAP bitmap = s_Video.createDIBSection( - hdc, - (BITMAPINFO*)&bitInfo, - DIB_RGB_COLORS, - &dibPixels, - nullptr, - 0); - - if (!bitmap) { - ReleaseDC(nullptr, hdc); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - ReleaseDC(nullptr, hdc); - - // convert RGBA to BGRA - Uint8* pixels = (Uint8*)dibPixels; - for (Uint32 i = 0; i < info->width * info->height; i++) { - Uint8 r = info->pixels[i * 4 + 0]; // Red - Uint8 g = info->pixels[i * 4 + 1]; // Green - Uint8 b = info->pixels[i * 4 + 2]; // Blue - Uint8 a = info->pixels[i * 4 + 3]; // Alpha - - // premultiply only if alpha is not 0 - if (a == 0) { - r = g = b = 0; - } else { - r = (Uint8)((r * a) / 255); - g = (Uint8)((g * a) / 255); - b = (Uint8)((b * a) / 255); - } - - pixels[i * 4 + 0] = b; - pixels[i * 4 + 1] = g; - pixels[i * 4 + 2] = r; - pixels[i * 4 + 3] = a; - } - - ICONINFO iconInfo = {0}; - iconInfo.fIcon = TRUE; - iconInfo.hbmMask = bitmap; - iconInfo.hbmColor = bitmap; - - // create the icon with the icon info - HICON icon = CreateIconIndirect(&iconInfo); - if (!icon) { - s_Video.deleteObject(bitmap); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_Video.deleteObject(bitmap); - *outIcon = (PalIcon*)icon; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyIcon(PalIcon* icon) -{ - if (s_Video.initialized && icon) { - DestroyIcon((HICON)icon); - } -} - -PalResult PAL_CALL palSetWindowIcon( - PalWindow* window, - PalIcon* icon) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (!IsWindow((HWND)window)) { - return PAL_RESULT_INVALID_WINDOW; - } - - SendMessageW((HWND)window, WM_SETICON, ICON_BIG, (LPARAM)icon); - SendMessageW((HWND)window, WM_SETICON, ICON_SMALL, (LPARAM)icon); - return PAL_RESULT_SUCCESS; -} - -// ================================================== -// Cursor -// ================================================== - -PalResult PAL_CALL palCreateCursor( - const PalCursorCreateInfo* info, - PalCursor** outCursor) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!info || !outCursor) { - return PAL_RESULT_NULL_POINTER; - } - - // describe the icon pixels - BITMAPV5HEADER bitInfo = {0}; - bitInfo.bV5Size = sizeof(BITMAPV5HEADER); - bitInfo.bV5Width = info->width; - bitInfo.bV5Height = -(Int32)info->height; // this is topdown by default - - // default parameters - bitInfo.bV5Planes = 1; - bitInfo.bV5BitCount = 32; // PAL supports 32 bits - bitInfo.bV5Compression = BI_BITFIELDS; - bitInfo.bV5RedMask = 0x00FF0000; - bitInfo.bV5GreenMask = 0x0000FF00; - bitInfo.bV5BlueMask = 0x000000FF; - bitInfo.bV5AlphaMask = 0xFF000000; - - HDC hdc = GetDC(nullptr); - void* dibPixels = nullptr; - - // create dib section - HBITMAP bitmap = s_Video.createDIBSection( - hdc, - (BITMAPINFO*)&bitInfo, - DIB_RGB_COLORS, - &dibPixels, - nullptr, - 0); - - if (!bitmap) { - ReleaseDC(nullptr, hdc); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - ReleaseDC(nullptr, hdc); - - // convert RGBA to BGRA - Uint8* pixels = (Uint8*)dibPixels; - for (Uint32 i = 0; i < info->width * info->height; i++) { - Uint8 r = info->pixels[i * 4 + 0]; // Red - Uint8 g = info->pixels[i * 4 + 1]; // Green - Uint8 b = info->pixels[i * 4 + 2]; // Blue - Uint8 a = info->pixels[i * 4 + 3]; // Alpha - - // premultiply only if alpha is not 0 - if (a == 0) { - r = g = b = 0; - } else { - r = (Uint8)((r * a) / 255); - g = (Uint8)((g * a) / 255); - b = (Uint8)((b * a) / 255); - } - - pixels[i * 4 + 0] = b; - pixels[i * 4 + 1] = g; - pixels[i * 4 + 2] = r; - pixels[i * 4 + 3] = a; - } - - ICONINFO iconInfo = {0}; - iconInfo.fIcon = false; - iconInfo.hbmColor = bitmap; - iconInfo.hbmMask = bitmap; - iconInfo.xHotspot = info->xHotspot; - iconInfo.xHotspot = info->yHotspot; - - // create the cursor with the iconinfo - HCURSOR cursor = CreateIconIndirect(&iconInfo); - if (!cursor) { - s_Video.deleteObject(bitmap); - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - s_Video.deleteObject(bitmap); - *outCursor = (PalCursor*)cursor; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palCreateCursorFrom( - PalCursorType type, - PalCursor** outCursor) -{ - - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!outCursor) { - return PAL_RESULT_NULL_POINTER; - } - - HCURSOR cursor = nullptr; - switch (type) { - case PAL_CURSOR_ARROW: { - cursor = LoadCursorW(nullptr, IDC_ARROW); - break; - } - - case PAL_CURSOR_HAND: { - cursor = LoadCursorW(nullptr, IDC_HAND); - break; - } - - case PAL_CURSOR_CROSS: { - cursor = LoadCursorW(nullptr, IDC_CROSS); - break; - } - - case PAL_CURSOR_IBEAM: { - cursor = LoadCursorW(nullptr, IDC_IBEAM); - break; - } - - case PAL_CURSOR_WAIT: { - cursor = LoadCursorW(nullptr, IDC_WAIT); - break; - } - - default: { - return PAL_RESULT_INVALID_ARGUMENT; - } - } - - if (!cursor) { - DWORD error = GetLastError(); - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - *outCursor = (PalCursor*)cursor; - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palDestroyCursor(PalCursor* cursor) -{ - if (s_Video.initialized && cursor) { - DestroyCursor((HCURSOR)cursor); - } -} - -void PAL_CALL palShowCursor(bool show) -{ - if (s_Video.initialized) { - ShowCursor(show); - } -} - -PalResult PAL_CALL palClipCursor( - PalWindow* window, - bool clip) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - if (clip) { - RECT rect; - if (!GetClientRect((HWND)window, &rect)) { - return PAL_RESULT_INVALID_WINDOW; - } - - POINT tmp = {rect.left, rect.top}; - POINT tmp2 = {rect.right, rect.bottom}; - - ClientToScreen((HWND)window, &tmp); - ClientToScreen((HWND)window, &tmp2); - - RECT clipRect = {tmp.x, tmp.y, tmp2.x, tmp2.y}; - ClipCursor(&clipRect); - - } else { - ClipCursor(nullptr); - } - - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palGetCursorPos( - PalWindow* window, - Int32* x, - Int32* y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - POINT pos; - GetCursorPos(&pos); - if (ScreenToClient((HWND)window, &pos)) { - if (x) { - *x = pos.x; - } - - if (y) { - *y = pos.y; - } - - return PAL_RESULT_SUCCESS; - - } else { - return PAL_RESULT_INVALID_WINDOW; - } -} - -PalResult PAL_CALL palSetCursorPos( - PalWindow* window, - Int32 x, - Int32 y) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - POINT pos = {x, y}; - if (!ClientToScreen((HWND)window, &pos)) { - return PAL_RESULT_INVALID_WINDOW; - } - - SetCursorPos(pos.x, pos.y); - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palSetWindowCursor( - PalWindow* window, - PalCursor* cursor) -{ - if (window) { - SetLastError(0); - WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - data->cursor = (HCURSOR)cursor; - DWORD error = GetLastError(); - if (error == 0) { - return PAL_RESULT_SUCCESS; - - } else if (error == ERROR_INVALID_HANDLE) { - return PAL_RESULT_INVALID_WINDOW; - - } else { - palSetLastPlatformError(error); - return PAL_RESULT_PLATFORM_FAILURE; - } - - } else { - return PAL_RESULT_NULL_POINTER; - } -} - -void* PAL_CALL palGetInstance() -{ - if (!s_Video.initialized) { - return nullptr; - } - - return (void*)s_Video.instance; -} - -PalResult PAL_CALL palAttachWindow( - void* windowHandle, - PalWindow** outWindow) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!windowHandle || !outWindow) { - return PAL_RESULT_NULL_POINTER; - } - - WindowData* data = getFreeWindowData(); - if (!data) { - return PAL_RESULT_OUT_OF_MEMORY; - } - - PalWindow* window = (PalWindow*)windowHandle; - data->isAttached = true; - data->wndProc = SetWindowLongPtrW( - (HWND)windowHandle, - GWLP_WNDPROC, - (LONG_PTR)videoProc); - - // use default PAL video cursor - // there is no way to get the cursor set on the native window - data->cursor = nullptr; - - // get state - palGetWindowState(window, &data->state); - SetPropW((HWND)window, PAL_VIDEO_PROP, data); - - *outWindow = window; - return PAL_RESULT_SUCCESS; -} - -PalResult PAL_CALL palDetachWindow( - PalWindow* window, - void** outWindowHandle) -{ - if (!s_Video.initialized) { - return PAL_RESULT_VIDEO_NOT_INITIALIZED; - } - - if (!window) { - return PAL_RESULT_NULL_POINTER; - } - - WindowData* data = nullptr; - data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); - if (!data) { - return PAL_RESULT_INVALID_WINDOW; - } - - if (data->isAttached == false) { - // window is owned by PAL - return PAL_RESULT_INVALID_WINDOW; - } - - data->used = false; - SetWindowLongPtrW((HWND)window, GWLP_WNDPROC, data->wndProc); - if (outWindowHandle) { - *outWindowHandle = (void*)window; - } - - return PAL_RESULT_SUCCESS; -} - -void PAL_CALL palSetPreferredInstance(void* instance) -{ - if (!s_Video.initialized && instance) { - s_Video.instance = instance; - } -} \ No newline at end of file diff --git a/src/video/wayland/pal_cursor_wayland.c b/src/video/wayland/pal_cursor_wayland.c new file mode 100644 index 00000000..fec80a89 --- /dev/null +++ b/src/video/wayland/pal_cursor_wayland.c @@ -0,0 +1,117 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland.h" +#include "pal_wayland_protocols.h" +#include + +PalResult wlCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + WaylandCursor* cursor = nullptr; + cursor = palAllocate(s_Wl.allocator, sizeof(WaylandCursor), 0); + if (!cursor) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!cursor->surface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + cursor->buffer = createShmBuffer(info->width, info->height, info->pixels, PAL_TRUE); + if (!cursor->buffer) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); + wlSurfaceCommit(cursor->surface); + cursor->hotspotX = info->xHotspot; + cursor->hotspotY = info->yHotspot; + + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} + +PalResult wlCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + const char* cursorType = nullptr; + switch (type) { + case PAL_CURSOR_TYPE_ARROW: { + cursorType = "left_ptr"; + break; + } + + case PAL_CURSOR_TYPE_HAND: { + cursorType = "hand1"; + break; + } + + case PAL_CURSOR_TYPE_CROSS: { + cursorType = "crosshair"; + break; + } + + case PAL_CURSOR_TYPE_IBEAM: { + cursorType = "text"; + break; + } + + case PAL_CURSOR_TYPE_WAIT: { + cursorType = "wait"; + break; + } + } + + struct wl_cursor* wlCursor = nullptr; + wlCursor = s_Wl.cursorThemeGetCursor(s_Wl.cursorTheme, cursorType); + if (!wlCursor) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + WaylandCursor* cursor = nullptr; + cursor = palAllocate(s_Wl.allocator, sizeof(WaylandCursor), 0); + if (!cursor) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + cursor->surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!cursor->surface) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + cursor->buffer = s_Wl.cursorImageGetBuffer(wlCursor->images[0]); + wlSurfaceAttach(cursor->surface, cursor->buffer, 0, 0); + wlSurfaceCommit(cursor->surface); + cursor->hotspotX = wlCursor->images[0]->hotspot_x; + cursor->hotspotY = wlCursor->images[0]->hotspot_y; + + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} + +void wlDestroyCursor(PalCursor* cursor) +{ + WaylandCursor* waylandCursor = (WaylandCursor*)cursor; + wlBufferDestroy(waylandCursor->buffer); + wlSurfaceDestroy(waylandCursor->surface); + palFree(s_Wl.allocator, waylandCursor); +} + +void wlSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + WindowData* data = wlFindWindowData(window); + data->cursor = cursor; +} + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/wayland/pal_icon_wayland.c b/src/video/wayland/pal_icon_wayland.c new file mode 100644 index 00000000..2f8eb1a3 --- /dev/null +++ b/src/video/wayland/pal_icon_wayland.c @@ -0,0 +1,23 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland.h" + +PalResult wlCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +void wlDestroyIcon(PalIcon* icon) +{ + return; +} + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/wayland/pal_monitor_wayland.c b/src/video/wayland/pal_monitor_wayland.c new file mode 100644 index 00000000..0d043575 --- /dev/null +++ b/src/video/wayland/pal_monitor_wayland.c @@ -0,0 +1,103 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland.h" + +PalResult wlEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitors) +{ + if (outMonitors) { + int index = 0; + int maxCount = s_Wl.maxMonitorData; + for (int i = 0; i < maxCount && index < *count; i++) { + if (s_Wl.monitorData[i].used) { + // found a monitor + PalMonitor* monitor = s_Wl.monitorData[index].monitor; + outMonitors[index++] = monitor; + } + } + } + + if (!outMonitors) { + *count = s_Wl.monitorCount; + } + + return PAL_RESULT_SUCCESS; +} + +void wlGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + MonitorData* monitorData = wlFindMonitorData(monitor); + info->dpi = monitorData->dpi; + info->x = monitorData->x; + info->y = monitorData->y; + info->width = monitorData->w; + info->height = monitorData->h; + info->refreshRate = monitorData->refreshRate; + info->orientation = monitorData->orientation; + + info->primary = PAL_FALSE; // no way to query + strcpy(info->name, monitorData->name); +} + +void wlEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* modes) +{ + MonitorData* monitorData = wlFindMonitorData(monitor); + if (modes && *count > 0) { + PalMonitorMode* mode = &modes[0]; + mode->bpp = monitorData->mode.bpp; + mode->width = monitorData->mode.width; + mode->height = monitorData->mode.height; + mode->refreshRate = monitorData->mode.refreshRate; + } + + if (!modes) { + *count = 1; // wayland only gives the active mode + } +} + +void wlGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + MonitorData* monitorData = wlFindMonitorData(monitor); + // this is the same as the current mode + mode->bpp = monitorData->mode.bpp; + mode->width = monitorData->mode.width; + mode->height = monitorData->mode.height; + mode->refreshRate = monitorData->mode.refreshRate; +} + +PalResult wlSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +PalResult wlValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +PalResult wlSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/wayland/pal_video_wayland.c b/src/video/wayland/pal_video_wayland.c new file mode 100644 index 00000000..fa5fc2a7 --- /dev/null +++ b/src/video/wayland/pal_video_wayland.c @@ -0,0 +1,1800 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#define _POSIX_C_SOURCE 200112L +#define _GNU_SOURCE +#include "pal_wayland.h" +#include "pal_wayland_protocols.h" +#include "video/pal_video_egl.h" +#include +#include +#include +#include + +typedef struct { + PalBool pendingScroll; + int32_t lastX; + int32_t lastY; + int32_t dx; + int32_t dy; + int32_t WheelX; + int32_t WheelY; + PalBool state[PAL_MOUSE_BUTTON_COUNT]; + double tmpScrollX; + double tmpScrollY; + double accumScrollX; + double accumScrollY; +} Mouse; + +typedef struct { + PalBool scancodeState[PAL_SCANCODE_COUNT]; + PalBool keycodeState[PAL_KEYCODE_COUNT]; + int repeatRate; + int repeatDelay; + int repeatKey; + int repeatScancode; + int scancodes[512]; + int keycodes[256]; + uint64_t timer; + uint64_t frequency; +} Keyboard; + +Wayland s_Wl = {0}; +static Mouse s_Mouse = {0}; +static Keyboard s_Keyboard = {0}; + +// ================================================== +// Helpers +// ================================================== + +static int createShmFile(uint64_t size) +{ + char template[] = "/tmp/pal-shm-XXXXXX"; + int fd = mkstemp(template); + if (fd < 0) { + return -1; + } + + unlink(template); + if (ftruncate(fd, size) < 0) { + return -1; + } + + return fd; +} + +struct wl_buffer* createShmBuffer( + int width, + int height, + const uint8_t* pixels, + PalBool cursor) +{ + int stride = width * 4; + uint64_t size = stride * height; + struct wl_buffer* buffer = nullptr; + struct wl_shm_pool* pool = nullptr; + void* data = nullptr; + + int fd = createShmFile(size); + if (fd == -1) { + return nullptr; + } + + data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + return nullptr; + } + + enum wl_shm_format format = WL_SHM_FORMAT_XRGB8888; + if (cursor) { + format = WL_SHM_FORMAT_ARGB8888; + uint32_t* dataPixels = (uint32_t*)data; + + // convert from RGBA8 to ARGB32 + for (int i = 0; i < width * height; i++) { + uint8_t r = pixels[i * 4 + 0]; // Red + uint8_t g = pixels[i * 4 + 1]; // Green + uint8_t b = pixels[i * 4 + 2]; // Blue + uint8_t a = pixels[i * 4 + 3]; // Alpha + + // clang-format off + dataPixels[i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } + + } else { + memset(data, 0xFF, size); // white + } + + pool = wlShmCreatePool(s_Wl.shm, fd, size); + if (!pool) { + return nullptr; + } + + buffer = wlShmPoolCreateBuffer(pool, 0, width, height, stride, format); + if (!buffer) { + return nullptr; + } + + wlShmPoolDestroy(pool); + munmap(data, size); + close(fd); + return buffer; +} + +static void createScancodeTable() +{ + // Letters + s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; + s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; + s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; + s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; + s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; + s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; + s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; + s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; + s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; + s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; + s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; + s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; + s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; + s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; + s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; + s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; + s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; + s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; + s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; + s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; + s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; + s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; + s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; + s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; + s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; + s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; + + // Numbers (top row) + s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; + s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; + s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; + s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; + s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; + s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; + s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; + s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; + s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; + s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; + + // Function + s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; + s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; + s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; + s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; + s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; + s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; + s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; + s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; + s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; + s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; + s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; + s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; + + // Control + s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; + s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; + s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; + s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; + s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; + s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; + s_Keyboard.scancodes[0x045] = PAL_SCANCODE_NUMLOCK; + s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; + s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; + s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; + s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; + s_Keyboard.scancodes[0x061] = PAL_SCANCODE_RCTRL; + s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; + s_Keyboard.scancodes[0x064] = PAL_SCANCODE_RALT; + + // Arrows + s_Keyboard.scancodes[0x069] = PAL_SCANCODE_LEFT; + s_Keyboard.scancodes[0x06A] = PAL_SCANCODE_RIGHT; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_UP; + s_Keyboard.scancodes[0x06C] = PAL_SCANCODE_DOWN; + + // Navigation + s_Keyboard.scancodes[0x06E] = PAL_SCANCODE_INSERT; + s_Keyboard.scancodes[0x06F] = PAL_SCANCODE_DELETE; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_HOME; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_END; + s_Keyboard.scancodes[0x068] = PAL_SCANCODE_PAGEUP; + s_Keyboard.scancodes[0x06D] = PAL_SCANCODE_PAGEDOWN; + + // Keypad + s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; + s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; + s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; + s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; + s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; + s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; + s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; + s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; + s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; + s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; + s_Keyboard.scancodes[0x060] = PAL_SCANCODE_KP_ENTER; + s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; + s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; + s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; + s_Keyboard.scancodes[0x062] = PAL_SCANCODE_KP_DIVIDE; + s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; + + // Misc + s_Keyboard.scancodes[0x063] = PAL_SCANCODE_PRINTSCREEN; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[0x07F] = PAL_SCANCODE_MENU; + s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; + s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; + s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; + s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; + s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; + s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; + s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; + s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; + s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; + s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; + s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; + s_Keyboard.scancodes[0x07D] = PAL_SCANCODE_LSUPER; + s_Keyboard.scancodes[0x07E] = PAL_SCANCODE_RSUPER; +} + +static void createKeycodeTable() +{ + // Tis is for only printable and text input keys + + // Letters + s_Keyboard.keycodes[XKB_KEY_a] = PAL_KEYCODE_A; + s_Keyboard.keycodes[XKB_KEY_b] = PAL_KEYCODE_B; + s_Keyboard.keycodes[XKB_KEY_c] = PAL_KEYCODE_C; + s_Keyboard.keycodes[XKB_KEY_d] = PAL_KEYCODE_D; + s_Keyboard.keycodes[XKB_KEY_e] = PAL_KEYCODE_E; + s_Keyboard.keycodes[XKB_KEY_f] = PAL_KEYCODE_F; + s_Keyboard.keycodes[XKB_KEY_g] = PAL_KEYCODE_G; + s_Keyboard.keycodes[XKB_KEY_h] = PAL_KEYCODE_H; + s_Keyboard.keycodes[XKB_KEY_i] = PAL_KEYCODE_I; + s_Keyboard.keycodes[XKB_KEY_j] = PAL_KEYCODE_J; + s_Keyboard.keycodes[XKB_KEY_k] = PAL_KEYCODE_K; + s_Keyboard.keycodes[XKB_KEY_l] = PAL_KEYCODE_L; + s_Keyboard.keycodes[XKB_KEY_m] = PAL_KEYCODE_M; + s_Keyboard.keycodes[XKB_KEY_n] = PAL_KEYCODE_N; + s_Keyboard.keycodes[XKB_KEY_o] = PAL_KEYCODE_O; + s_Keyboard.keycodes[XKB_KEY_p] = PAL_KEYCODE_P; + s_Keyboard.keycodes[XKB_KEY_q] = PAL_KEYCODE_Q; + s_Keyboard.keycodes[XKB_KEY_r] = PAL_KEYCODE_R; + s_Keyboard.keycodes[XKB_KEY_s] = PAL_KEYCODE_S; + s_Keyboard.keycodes[XKB_KEY_t] = PAL_KEYCODE_T; + s_Keyboard.keycodes[XKB_KEY_u] = PAL_KEYCODE_U; + s_Keyboard.keycodes[XKB_KEY_v] = PAL_KEYCODE_V; + s_Keyboard.keycodes[XKB_KEY_w] = PAL_KEYCODE_W; + s_Keyboard.keycodes[XKB_KEY_x] = PAL_KEYCODE_X; + s_Keyboard.keycodes[XKB_KEY_y] = PAL_KEYCODE_Y; + s_Keyboard.keycodes[XKB_KEY_z] = PAL_KEYCODE_Z; + + // Control + s_Keyboard.keycodes[XKB_KEY_space] = PAL_KEYCODE_SPACE; + + // Misc + s_Keyboard.keycodes[XKB_KEY_apostrophe] = PAL_KEYCODE_APOSTROPHE; + s_Keyboard.keycodes[XKB_KEY_backslash] = PAL_KEYCODE_BACKSLASH; + s_Keyboard.keycodes[XKB_KEY_comma] = PAL_KEYCODE_COMMA; + s_Keyboard.keycodes[XKB_KEY_equal] = PAL_KEYCODE_EQUAL; + s_Keyboard.keycodes[XKB_KEY_grave] = PAL_KEYCODE_GRAVEACCENT; + s_Keyboard.keycodes[XKB_KEY_minus] = PAL_KEYCODE_SUBTRACT; + s_Keyboard.keycodes[XKB_KEY_period] = PAL_KEYCODE_PERIOD; + s_Keyboard.keycodes[XKB_KEY_semicolon] = PAL_KEYCODE_SEMICOLON; + s_Keyboard.keycodes[XKB_KEY_slash] = PAL_KEYCODE_SLASH; + s_Keyboard.keycodes[XKB_KEY_bracketleft] = PAL_KEYCODE_LBRACKET; + s_Keyboard.keycodes[XKB_KEY_bracketright] = PAL_KEYCODE_RBRACKET; +} + +MonitorData* wlGetFreeMonitorData() +{ + for (int i = 0; i < s_Wl.maxMonitorData; ++i) { + if (!s_Wl.monitorData[i].used) { + s_Wl.monitorData[i].used = PAL_TRUE; + return &s_Wl.monitorData[i]; + } + } + + // resize the data array + // this will almost not reach here since most setups are 1-4 monitors + MonitorData* data = nullptr; + int count = s_Wl.maxMonitorData * 2; // double the size + int freeIndex = s_Wl.maxMonitorData + 1; + data = palAllocate(s_Wl.allocator, sizeof(MonitorData) * count, 0); + if (data) { + memcpy(data, s_Wl.monitorData, s_Wl.maxMonitorData * sizeof(MonitorData)); + + palFree(s_Wl.allocator, s_Wl.monitorData); + s_Wl.monitorData = data; + s_Wl.maxWindowData = count; + + s_Wl.monitorData[freeIndex].used = PAL_TRUE; + return &s_Wl.monitorData[freeIndex]; + } + return nullptr; +} + +MonitorData* wlFindMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_Wl.maxMonitorData; ++i) { + if (s_Wl.monitorData[i].used && s_Wl.monitorData[i].monitor == monitor) { + return &s_Wl.monitorData[i]; + } + } + return nullptr; +} + +void wlFreeMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_Wl.maxMonitorData; ++i) { + if (s_Wl.monitorData[i].used && s_Wl.monitorData[i].monitor == monitor) { + s_Wl.monitorData[i].used = PAL_FALSE; + } + } +} + +WindowData* wlGetFreeWindowData() +{ + for (int i = 0; i < s_Wl.maxWindowData; ++i) { + if (!s_Wl.windowData[i].used) { + s_Wl.windowData[i].used = PAL_TRUE; + return &s_Wl.windowData[i]; + } + } + + // resize the data array + // It is rare for a user to create and manage + // 32 windows at the same time + WindowData* data = nullptr; + int count = s_Wl.maxWindowData * 2; // double the size + int freeIndex = s_Wl.maxWindowData + 1; + data = palAllocate(s_Wl.allocator, sizeof(WindowData) * count, 0); + if (data) { + memcpy(data, s_Wl.windowData, s_Wl.maxWindowData * sizeof(WindowData)); + + palFree(s_Wl.allocator, s_Wl.windowData); + s_Wl.windowData = data; + s_Wl.maxWindowData = count; + + s_Wl.windowData[freeIndex].used = PAL_TRUE; + return &s_Wl.windowData[freeIndex]; + } + return nullptr; +} + +WindowData* wlFindWindowData(PalWindow* window) +{ + for (int i = 0; i < s_Wl.maxWindowData; ++i) { + if (s_Wl.windowData[i].used && s_Wl.windowData[i].window == window) { + return &s_Wl.windowData[i]; + } + } + return nullptr; +} + +static inline uint64_t getCurrentTime() +{ + uint64_t now = palGetPerformanceCounter(); + return (now * 1000) / s_Keyboard.frequency; +} + +// ================================================== +// Registry +// ================================================== + +extern struct wl_interface xdg_wm_base_interface; + +static void globalHandle( + void* data, + struct wl_registry* registry, + uint32_t name, + const char* interface, + uint32_t version) +{ + if (s_Wl.checkFeatures) { + PalVideoFeatures features = 0; + features |= PAL_VIDEO_FEATURE_HIGH_DPI; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; + features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR; + + s_Wl.features = features; + s_Wl.checkFeatures = PAL_FALSE; + } + + if (strcmp(interface, "wl_compositor") == 0) { + s_Wl.compositor = wlRegistryBind(registry, name, s_Wl.compositorInterface, 4); + + } else if (strcmp(interface, "xdg_wm_base") == 0) { + s_Wl.xdgBase = wlRegistryBind(registry, name, &xdg_wm_base_interface, 1); + + xdgWmBaseAddListener(s_Wl.xdgBase, &s_WmBaseListener, nullptr); + + } else if (strcmp(interface, "wl_shm") == 0) { + s_Wl.shm = wlRegistryBind(registry, name, s_Wl.shmInterface, 1); + + } else if (strcmp(interface, "wl_seat") == 0) { + s_Wl.seat = wlRegistryBind(registry, name, s_Wl.seatInterface, 5); + + wlSeatAddListener(s_Wl.seat, &s_SeatListener, nullptr); + + } else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0) { + s_Wl.decorationManager = + wlRegistryBind(registry, name, &zxdg_decoration_manager_v1_interface, 1); + + s_Wl.features |= PAL_VIDEO_FEATURE_DECORATED_WINDOW; + + } else if (strcmp(interface, "wl_output") == 0) { + // wayland does not let use query monitors directly + // so we enumerate and store at init and update the + // cache when a monitor is added or removed + MonitorData* monitorData = wlGetFreeMonitorData(); + if (!monitorData) { + return; + } + + struct wl_output* output = nullptr; + output = wlRegistryBind(s_Wl.registry, name, s_Wl.outputInterface, 3); + wlOutputAddListener(output, &s_OutputListener, monitorData); + + monitorData->wlName = name; + monitorData->monitor = (PalMonitor*)output; + s_Wl.monitorCount++; + } +} + +static void globalRemove( + void* data, + struct wl_registry* registry, + uint32_t name) +{ + for (int i = 0; i < s_Wl.maxMonitorData; ++i) { + if (s_Wl.monitorData[i].used && s_Wl.monitorData[i].wlName == name) { + MonitorData* data = &s_Wl.monitorData[i]; + data->used = PAL_FALSE; + s_Wl.proxyDestroy((struct wl_proxy*)data->monitor); + s_Wl.monitorCount--; + } + } +} + +// ================================================== +// Output +// ================================================== + +static void outputGeometry( + void* data, + struct wl_output* output, + int32_t x, + int32_t y, + int32_t tmp, // we dont need physical size + int32_t tmp2, // we dont need physical size + int32_t tmp3, // we dont need subpixel + const char* make, + const char* model, + int32_t transform) +{ + MonitorData* monitorData = data; + monitorData->x = x; + monitorData->y = y; + + switch (transform) { + case WL_OUTPUT_TRANSFORM_NORMAL: + case WL_OUTPUT_TRANSFORM_180: { + monitorData->orientation = PAL_ORIENTATION_LANDSCAPE; + break; + } + + case WL_OUTPUT_TRANSFORM_90: + case WL_OUTPUT_TRANSFORM_270: { + monitorData->orientation = PAL_ORIENTATION_PORTRAIT; + break; + } + + case WL_OUTPUT_TRANSFORM_FLIPPED: + case WL_OUTPUT_TRANSFORM_FLIPPED_180: { + monitorData->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + case WL_OUTPUT_TRANSFORM_FLIPPED_90: + case WL_OUTPUT_TRANSFORM_FLIPPED_270: { + monitorData->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; + break; + } + } + + snprintf(monitorData->name, 32, "%s %s", make, model); +} + +static void outputMode( + void* data, + struct wl_output* output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ + MonitorData* monitorData = data; + if (flags & WL_OUTPUT_MODE_CURRENT) { + monitorData->w = width; + monitorData->h = height; + monitorData->refreshRate = (refresh + 500) / 1000; + + // wayland only sends the current mode + monitorData->mode.bpp = 0; + monitorData->mode.width = width; + monitorData->mode.height = height; + monitorData->mode.refreshRate = (refresh + 500) / 1000; + } +} + +static void outputScale( + void* data, + struct wl_output* output, + int32_t scale) +{ + MonitorData* monitorData = data; + float dpi = (float)scale * 96.0f; + monitorData->dpi = (uint32_t)dpi; +} + +static void outputDone( + void* data, + struct wl_output* output) +{ +} + +// ================================================== +// Surface +// ================================================== + +static void surfaceHandleEnter( + void* userData, + struct wl_surface* surface, + struct wl_output* output) +{ + WindowData* data = userData; + MonitorData* monitorData = wlFindMonitorData((PalMonitor*)output); + if (!monitorData) { + return; + } + + // wayland sends multiple surface enter events + // if the surface spans multiple monitors + // we get all and return the highest dpi + // this assumes a surface can only span 4 monitors + // at the sametime but this might be wrong + + // wayland will trigger this event if the window gains focus + // so we check if the output is the same + SpanMonitor* span = nullptr; + if (data->monitorCount > 0) { + for (int i = 0; i < data->monitorCount; i++) { + if (data->monitors[i].monitor == (void*)output) { + // the monitor already exist in our array + // so we just update it + span = &data->monitors[i]; + span->dpi = monitorData->dpi; + break; + } + } + } + + if (span == nullptr) { + // new entry + span = &data->monitors[data->monitorCount]; + span->monitor = output; + span->dpi = monitorData->dpi; + data->monitorCount++; + } + + if (data->dpi == 0) { + // this is triggered when the window is created + // we cache the DPI and skip the event + data->dpi = monitorData->dpi; + return; + } + + // the code below should be skipped if users are not + // interested in DPI changed events + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + PalEventType type = PAL_EVENT_TYPE_MONITOR_DPI_CHANGED; + if (!s_Wl.eventDriver) { + return; + } + + PalEventDriver* driver = s_Wl.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_MODE_NONE) { + return; + } + + // get the highest dpi and check if the it has changed + int maxDPI = 96; // baseline + for (int i = 0; i < data->monitorCount; i++) { + if (!data->monitors[i].monitor) { + // not a valid index. continue + continue; + } + + if (data->monitors[i].dpi > maxDPI) { + // new highest + maxDPI = data->monitors[i].dpi; + } + } + + if (maxDPI != data->dpi) { + data->dpi = maxDPI; + + PalEvent event = {0}; + event.type = type; + event.data = maxDPI; + event.data2 = palPackPointer((void*)data->window); + palPushEvent(driver, &event); + } +} + +static void surfaceHandleLeave( + void* userData, + struct wl_surface* surface, + struct wl_output* output) +{ + // remove the monitor from our span monitor list + WindowData* data = userData; + MonitorData* monitorData = wlFindMonitorData((PalMonitor*)output); + if (!monitorData) { + return; + } + + for (int i = 0; i < data->monitorCount; i++) { + if (data->monitors[i].monitor == (void*)output) { + // found our monitor + // we might want to pack the array but its just 4 monitors + // so we leave it like that + data->monitors[i].monitor = nullptr; + data->monitorCount--; + break; + } + } +} + +// ================================================== +// Pointer +// ================================================== + +static void pointerHandleEnter( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + WindowData* data = wlFindWindowData((PalWindow*)surface); + if (!data) { + return; + } + + if (data->cursor) { + // our window + WaylandCursor* cursor = data->cursor; + wlPointerSetCursor(pointer, serial, cursor->surface, cursor->hotspotX, cursor->hotspotY); + } + + // cache the surface the pointer is currently on + s_Wl.pointerSurface = surface; +} + +static void pointerHandleLeave( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + struct wl_surface* surface) +{ + if (s_Wl.pointerSurface == surface) { + s_Wl.pointerSurface = nullptr; + } +} + +static void pointerHandleMotion( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + int x = wl_fixed_to_int(surface_x); + int y = wl_fixed_to_int(surface_y); + const int dx = x - s_Mouse.lastX; + const int dy = y - s_Mouse.lastY; + + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + PalWindow* window = (PalWindow*)s_Wl.pointerSurface; + if (s_Wl.eventDriver && window) { + // we only push a mouse move only if we are on a window + PalEventDriver* driver = s_Wl.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MOUSE_MOVE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(x, y); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + // push a mouse delta event + type = PAL_EVENT_TYPE_MOUSE_DELTA; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackFloat((float)dx, (float)dy); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + s_Mouse.lastX = x; + s_Mouse.lastY = y; + s_Mouse.dx = dx; + s_Mouse.dy = dy; +} + +static void pointerHandleButton( + void* userData, + struct wl_pointer* pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + PalWindow* window = nullptr; + if (s_Wl.pointerSurface) { + window = (PalWindow*)s_Wl.pointerSurface; + + } else { + // cannot recieve events without a focused surface + return; + } + + PalBool pressed = state == WL_POINTER_BUTTON_STATE_PRESSED; + PalMouseButton _button = 0; + PalEventType type = PAL_EVENT_TYPE_MOUSE_BUTTONUP; + + if (button == BTN_LEFT) { + _button = PAL_MOUSE_BUTTON_LEFT; + + } else if (button == BTN_RIGHT) { + _button = PAL_MOUSE_BUTTON_RIGHT; + + } else if (button == BTN_MIDDLE) { + _button = PAL_MOUSE_BUTTON_MIDDLE; + + } else if (button == BTN_SIDE) { + _button = PAL_MOUSE_BUTTON_X1; + + } else if (button == BTN_EXTRA) { + _button = PAL_MOUSE_BUTTON_X2; + } + + if (pressed) { + type = PAL_EVENT_TYPE_MOUSE_BUTTONDOWN; + } + + s_Mouse.state[_button] = pressed; + if (s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + + // since we are not drawing decorations for users + // they need the serial in order to draw their decorations + // we put the serial at the upper 32 so ABI is preserved + event.data = palPackUint32(_button, serial); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } +} + +static void pointerHandleAxis( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ + double delta = wl_fixed_to_double(value); + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + s_Mouse.tmpScrollX += delta; + s_Mouse.accumScrollX += delta; + + } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + s_Mouse.tmpScrollY += delta; + s_Mouse.accumScrollY += delta; + } + + s_Mouse.pendingScroll = PAL_TRUE; +} + +static void pointerHandleAxisDiscrete( + void* userData, + struct wl_pointer* pointer, + uint32_t axis, + int32_t discrete) +{ + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + s_Mouse.tmpScrollX += discrete; + s_Mouse.accumScrollX += discrete; + + } else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + s_Mouse.tmpScrollY += discrete; + s_Mouse.accumScrollY += discrete; + } + + s_Mouse.pendingScroll = PAL_TRUE; +} + +static void pointerHandleFrame( + void* userData, + struct wl_pointer* pointer) +{ + if (!s_Mouse.pendingScroll) { + // no wheel event + return; + } + + PalWindow* window = nullptr; + if (s_Wl.pointerSurface) { + window = (PalWindow*)s_Wl.pointerSurface; + + } else { + // cannot recieve events without a focused surface + return; + } + + const int dx = (int)s_Mouse.accumScrollX; + const int dy = (int)s_Mouse.accumScrollY; + if (s_Wl.eventDriver) { + PalEventType type = PAL_EVENT_TYPE_MOUSE_WHEEL; + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackFloat((float)dx, (float)dy); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + s_Mouse.WheelX = dx; + s_Mouse.WheelY = dy; + s_Mouse.accumScrollX -= dx; + s_Mouse.accumScrollY -= dy; + s_Mouse.pendingScroll = PAL_FALSE; +} + +static void pointerHandleAxisSource( + void* userData, + struct wl_pointer* pointer, + uint32_t axis_source) +{ +} + +static void pointerHandleAxisStop( + void* userData, + struct wl_pointer* pointer, + uint32_t time, + uint32_t axis) +{ +} + +// ================================================== +// Keyboard +// ================================================== + +static void keyboardHandleEnter( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface, + struct wl_array* keys) +{ + // cache the surface the keyboard is currently on + s_Wl.keyboardSurface = surface; +} + +static void keyboardHandleLeave( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + struct wl_surface* surface) +{ + if (s_Wl.keyboardSurface == surface) { + s_Wl.keyboardSurface = nullptr; + } +} + +static void keyboardHandleRemap( + void* userData, + struct wl_keyboard* keyboard, + uint32_t format, + int32_t fd, + uint32_t size) +{ + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + struct xkb_keymap* keymap = nullptr; + struct xkb_state* state = nullptr; + + char* keymapStr = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0); + if (keymapStr == MAP_FAILED) { + close(fd); + return; + } + + keymap = s_Wl.xkbKeymapNewFromString( + s_Wl.inputContext, + keymapStr, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + if (!keymap) { + return; + } + + munmap(keymapStr, size); + close(fd); + + state = s_Wl.xkbStateNew(keymap); + if (!state) { + return; + } + + // check if we have old keymap and state + if (s_Wl.state) { + s_Wl.xkbStateUnref(s_Wl.state); + s_Wl.xkbKeymapUnref(s_Wl.keymap); + } + + s_Wl.state = state; + s_Wl.keymap = keymap; + s_Keyboard.frequency = palGetPerformanceFrequency(); +} + +static void keyboardHandleKey( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) +{ + PalWindow* window = nullptr; + if (s_Wl.keyboardSurface) { + window = (PalWindow*)s_Wl.keyboardSurface; + + } else { + // cannot recieve events without a focused surface + return; + } + + PalScancode scancode = 0; + PalKeycode keycode = 0; + PalBool pressed = (state == WL_KEYBOARD_KEY_STATE_PRESSED); + PalEventType type = PAL_EVENT_TYPE_KEYUP; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + xkb_keysym_t keySym = s_Wl.xkbStateKeyGetOneSym(s_Wl.state, key + 8); + + // special handling + if (key == 119) { + scancode = PAL_SCANCODE_PAUSE; + } else if (key == 107) { + scancode = PAL_SCANCODE_END; + } else if (key == 103) { + scancode = PAL_SCANCODE_UP; + } else if (key == 102) { + scancode = PAL_SCANCODE_HOME; + + } else { + scancode = s_Keyboard.scancodes[key]; + } + + // printable and text input keys are from the range + // 32 (PAL_KEYCODE_SPACE) and 122 (PAL_KEYCODE_Z) + // The rest are almost the same as their scancode + // Maybe there will be a layout that makes this wrong + // but for now this works + if (keySym >= XKB_KEY_space && keySym <= XKB_KEY_z) { + // a printable or input key + keycode = s_Keyboard.keycodes[keySym]; + + } else { + // Since PalKeycode and PalScancode have the same integers + // we can make a direct cast without a table + // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) + keycode = (PalKeycode)(uint32_t)scancode; + } + + // If we got a keySym but its not mapped into our keycode array + // we do a direct cast as well + if (keycode == PAL_KEYCODE_UNKNOWN) { + keycode = (PalKeycode)(uint32_t)scancode; + } + + s_Keyboard.scancodeState[scancode] = pressed; + s_Keyboard.keycodeState[keycode] = pressed; + + // check for key repeats + if (pressed) { + if (s_Wl.xkbKeymapKeyRepeats(s_Wl.keymap, key + 8)) { + s_Keyboard.repeatKey = keycode; + s_Keyboard.repeatScancode = scancode; + s_Keyboard.timer = getCurrentTime() + s_Keyboard.repeatDelay; + + } else { + s_Keyboard.repeatKey = 0; + } + + type = PAL_EVENT_TYPE_KEYDOWN; + + } else { + // key release + if (s_Keyboard.repeatKey == keycode) { + s_Keyboard.repeatKey = 0; + } + } + + if (s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + // check for char event if enabled + type = PAL_EVENT_TYPE_KEYCHAR; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_MODE_NONE) { + return; + } + + uint32_t codepoint = s_Wl.xkbKeysymToUtf32(keySym); + if (codepoint <= 0) { + return; + } + + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } +} + +static void keyboardHandleRepeatInfo( + void* userData, + struct wl_keyboard* keyboard, + int32_t rate, + int32_t delay) +{ + if (s_Wl.keyboard == keyboard) { + s_Keyboard.repeatDelay = delay; + s_Keyboard.repeatRate = rate; + + } else { + if (s_Keyboard.repeatDelay == 0) { + s_Keyboard.repeatDelay = 500; + s_Keyboard.repeatRate = 30; + } + } +} + +static void keyboardHandleModifiers( + void* userData, + struct wl_keyboard* keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + if (s_Wl.state) { + s_Wl.xkbStateUpdateMask(s_Wl.state, mods_depressed, mods_latched, mods_locked, group, 0, 0); + } +} + +// ================================================== +// Seat +// ================================================== + +static void seatHandleCapabilities( + void* userData, + struct wl_seat* seat, + enum wl_seat_capability caps) +{ + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + s_Wl.keyboard = wlSeatGetKeyboard(seat); + wlKeyboardAddListener(s_Wl.keyboard, &s_KeyboardListener, nullptr); + } + + if (caps & WL_SEAT_CAPABILITY_POINTER) { + s_Wl.pointer = wlSeatGetPointer(seat); + wlPointerAddListener(s_Wl.pointer, &s_PointerListener, nullptr); + } +} + +static void seatHandleName( + void* userData, + struct wl_seat* seat, + const char* name) +{ +} + +// ================================================== +// Xdg +// ================================================== + +static void wmBaseHandlePing( + void* data, + struct xdg_wm_base* base, + uint32_t serial) +{ + xdgWmBasePong(base, serial); +} + +static void xdgSurfaceHandleConfigure( + void* data, + struct xdg_surface* surface, + uint32_t serial) +{ + WindowData* winData = (WindowData*)data; + xdgSurfaceAckConfigure(surface, serial); + + // push and resolve any pending events + if (!winData->skipConfigure) { + if (winData->pushConfigureEvent) { + if (winData->eglWindow) { + s_Wl.eglWindowResize(winData->eglWindow, winData->w, winData->h, 0, 0); + + } else { + // create a new buffer with the new size + struct wl_buffer* buffer = nullptr; + buffer = createShmBuffer(winData->w, winData->h, nullptr, PAL_FALSE); + if (!buffer) { + return; + } + + struct wl_surface* _surface = nullptr; + _surface = (struct wl_surface*)winData->window; + wlSurfaceAttach(_surface, buffer, 0, 0); + wlSurfaceDamageBuffer(_surface, 0, 0, winData->w, winData->h); + wlSurfaceCommit(_surface); + + // destroy old buffer + wlBufferDestroy(winData->buffer); + winData->buffer = buffer; + } + + // push a window resize event + if (s_Wl.eventDriver) { + PalEventType type = PAL_EVENT_TYPE_WINDOW_SIZE; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(s_Wl.eventDriver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(winData->w, winData->h); + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Wl.eventDriver, &event); + } + } + + winData->pushConfigureEvent = PAL_FALSE; + } + } + + // pending state + if (!winData->skipState) { + if (!winData->pushStateEvent) { + return; + } + + // push a window state event + // we dont recreate buffers over here + // since we already create the buffer with the new size + if (s_Wl.eventDriver) { + PalEventType type = PAL_EVENT_TYPE_WINDOW_STATE; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(s_Wl.eventDriver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = winData->state; + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Wl.eventDriver, &event); + } + } + + winData->pushStateEvent = PAL_FALSE; + } +} + +static void xdgToplevelHandleConfigure( + void* data, + struct xdg_toplevel* toplevel, + int32_t width, + int32_t height, + struct wl_array* states) +{ + WindowData* winData = (WindowData*)data; + uint32_t* state; + PalBool activated = PAL_FALSE; + wl_array_for_each(state, states) + { + // we need only maximized + if (*state == 1) { // XDG_TOPLEVEL_STATE_MAXIMIZED + if (winData->state != PAL_WINDOW_STATE_MAXIMIZED) { + winData->state = PAL_WINDOW_STATE_MAXIMIZED; + winData->pushStateEvent = PAL_TRUE; + } + + } else if (*state == 4) { // XDG_TOPLEVEL_STATE_ACTIVATED + activated = PAL_TRUE; + } + } + + if (width > 0 && height > 0) { + if (width != winData->w || height != winData->h) { + // size change + winData->pushConfigureEvent = PAL_TRUE; + } + + winData->w = width; + winData->h = height; + } + + if (activated && !winData->focused) { + // focus gained + winData->focused = PAL_TRUE; + + } else if (!activated && winData->focused) { + // focus lost + winData->focused = PAL_FALSE; + + } else { + // discard double focus gained and double focus lost + return; + } + + if (s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + PalEventType type = PAL_EVENT_TYPE_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = winData->focused; + event.data2 = palPackPointer(winData->window); + palPushEvent(driver, &event); + } + } +} + +static void xdgToplevelHandleClose( + void* data, + struct xdg_toplevel* toplevel) +{ + WindowData* winData = (WindowData*)data; + if (s_Wl.eventDriver) { + PalEventType type = PAL_EVENT_TYPE_WINDOW_CLOSE; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(s_Wl.eventDriver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(winData->window); + palPushEvent(s_Wl.eventDriver, &event); + } + } +} + +// ================================================== +// Zxdg Decoration +// ================================================== + +void zxdgDecorationHandleConfigure( + void* data, + struct zxdg_toplevel_decoration_v1* dec, + uint32_t mode) +{ + if (s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode dispatchMode = PAL_DISPATCH_MODE_NONE; + PalEventType type = PAL_EVENT_TYPE_WINDOW_DECORATION_MODE; + dispatchMode = palGetEventDispatchMode(driver, type); + + if (dispatchMode != PAL_DISPATCH_MODE_NONE) { + PalDecorationMode decorMode = PAL_DECORATION_MODE_SERVER_SIDE; + if (mode == 0 || mode == 1) { + // client side decoration + decorMode = PAL_DECORATION_MODE_CLIENT_SIDE; + } + + PalEvent event = {0}; + event.type = type; + event.data = decorMode; + event.data2 = palPackPointer(data); + palPushEvent(driver, &event); + } + } +} + +PalResult wlInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance) +{ + // load wayland libray + s_Wl.handle = dlopen("libwayland-client.so.0", RTLD_LAZY); + s_Wl.xkbCommon = dlopen("libxkbcommon.so", RTLD_LAZY); + s_Wl.libCursor = dlopen("libwayland-cursor.so", RTLD_LAZY); + s_Wl.libWaylandEgl = dlopen("libwayland-egl.so", RTLD_LAZY); + + // clang-format off + if (!s_Wl.handle || + !s_Wl.xkbCommon || + !s_Wl.libCursor || + !s_Wl.libWaylandEgl) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_POSIX, + errno); + } + + // get the exported global variables + s_Wl.outputInterface = dlsym(s_Wl.handle, "wl_output_interface"); + s_Wl.seatInterface = dlsym(s_Wl.handle, "wl_seat_interface"); + s_Wl.compositorInterface = dlsym(s_Wl.handle, "wl_compositor_interface"); + s_Wl.surfaceInterface = dlsym(s_Wl.handle, "wl_surface_interface"); + s_Wl.registryInterface = dlsym(s_Wl.handle, "wl_registry_interface"); + s_Wl.shmInterface = dlsym(s_Wl.handle, "wl_shm_interface"); + s_Wl.bufferInterface = dlsym(s_Wl.handle, "wl_buffer_interface"); + s_Wl.shmPoolInterface = dlsym(s_Wl.handle, "wl_shm_pool_interface"); + s_Wl.regionInterface = dlsym(s_Wl.handle, "wl_region_interface"); + s_Wl.pointerInterface = dlsym(s_Wl.handle, "wl_pointer_interface"); + s_Wl.keyboardInterface = dlsym(s_Wl.handle, "wl_keyboard_interface"); + + // load function procs + s_Wl.displayConnect = (wl_display_connect_fn)dlsym( + s_Wl.handle, + "wl_display_connect"); + + s_Wl.displayDisconnect = (wl_display_disconnect_fn)dlsym( + s_Wl.handle, + "wl_display_disconnect"); + + s_Wl.displayRoundtrip = (wl_display_roundtrip_fn)dlsym( + s_Wl.handle, + "wl_display_roundtrip"); + + s_Wl.displayDispatch = (wl_display_dispatch_fn)dlsym( + s_Wl.handle, + "wl_display_dispatch"); + + s_Wl.proxyAddListener = (wl_proxy_add_listener_fn)dlsym( + s_Wl.handle, + "wl_proxy_add_listener"); + + s_Wl.proxyMarshalCnstructor = (wl_proxy_marshal_constructor_v_fn)dlsym( + s_Wl.handle, + "wl_proxy_marshal_constructor_versioned"); + + s_Wl.proxyDestroy = (wl_proxy_destroy_fn)dlsym( + s_Wl.handle, + "wl_proxy_destroy"); + + s_Wl.proxyMarshalFlags = (wl_proxy_marshal_flags_fn)dlsym( + s_Wl.handle, + "wl_proxy_marshal_flags"); + + s_Wl.proxyGetVersion = (wl_proxy_get_version_fn)dlsym( + s_Wl.handle, + "wl_proxy_get_version"); + + s_Wl.getError = (wl_display_get_error_fn)dlsym( + s_Wl.handle, + "wl_display_get_error"); + + s_Wl.dispatchPending = (wl_display_dispatch_pending_fn)dlsym( + s_Wl.handle, + "wl_display_dispatch_pending"); + + s_Wl.displayFlush = (wl_display_flush_fn)dlsym( + s_Wl.handle, + "wl_display_flush"); + + s_Wl.prepareRead = (wl_display_prepare_read_fn)dlsym( + s_Wl.handle, + "wl_display_prepare_read"); + + s_Wl.readEvents = (wl_display_read_events_fn)dlsym( + s_Wl.handle, + "wl_display_read_events"); + + s_Wl.displayGetFd = (wl_display_get_fd_fn)dlsym( + s_Wl.handle, + "wl_display_get_fd"); + + s_Wl.cancelRead = (wl_display_cancel_read_fn)dlsym( + s_Wl.handle, + "wl_display_cancel_read"); + + // load xkbcommon procs + s_Wl.xkbKeymapUnref = (xkb_keymap_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_unref"); + + s_Wl.xkbStateKeyGetOneSym = (xkb_state_key_get_one_sym_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_key_get_one_sym"); + + s_Wl.xkbStateNew = (xkb_state_new_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_new"); + + s_Wl.xkbStateUnref = (xkb_state_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_unref"); + + s_Wl.xkbContextUnref = (xkb_context_unref_fn)dlsym( + s_Wl.xkbCommon, + "xkb_context_unref"); + + s_Wl.xkbContextNew = (xkb_context_new_fn)dlsym( + s_Wl.xkbCommon, + "xkb_context_new"); + + s_Wl.xkbKeymapNewFromString = (xkb_keymap_new_from_string_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_new_from_string"); + + s_Wl.xkbKeysymToUtf32 = (xkb_keysym_to_utf32_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keysym_to_utf32"); + + s_Wl.xkbStateUpdateMask = (xkb_state_update_mask_fn)dlsym( + s_Wl.xkbCommon, + "xkb_state_update_mask"); + + s_Wl.xkbKeymapKeyRepeats = (xkb_keymap_key_repeats_fn)dlsym( + s_Wl.xkbCommon, + "xkb_keymap_key_repeats"); + + // load wayland cursor procs + s_Wl.cursorImageGetBuffer = (wl_cursor_image_get_buffer_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_image_get_buffer"); + + s_Wl.cursorThemeLoad = (wl_cursor_theme_load_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_theme_load"); + + s_Wl.cursorThemeGetCursor = (wl_cursor_theme_get_cursor_fn)dlsym( + s_Wl.libCursor, + "wl_cursor_theme_get_cursor"); + + // wl_egl procs + s_Wl.eglWindowCreate = (wl_egl_window_create_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_create"); + + s_Wl.eglWindowDestroy = (wl_egl_window_destroy_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_destroy"); + + s_Wl.eglWindowResize = (wl_egl_window_resize_fn)dlsym( + s_Wl.libWaylandEgl, + "wl_egl_window_resize"); + // clang-format on + + s_Wl.maxMonitorData = 16; // initial size + s_Wl.maxWindowData = 32; // initial size + s_Wl.windowData = palAllocate(s_Wl.allocator, sizeof(WindowData) * s_Wl.maxWindowData, 0); + s_Wl.monitorData = palAllocate(s_Wl.allocator, sizeof(MonitorData) * s_Wl.maxMonitorData, 0); + if (!s_Wl.monitorData || !s_Wl.windowData) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // initialize wayland + s_Wl.checkFeatures = PAL_TRUE; + s_Wl.monitorCount = 0; + setupProtocols(); + + // check if user supplied their own display + if (preferredInstance) { + s_Wl.display = (struct wl_display*)preferredInstance; + + } else { + s_Wl.display = s_Wl.displayConnect(nullptr); + if (!s_Wl.display) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + } + + s_Wl.display = (void*)s_Wl.display; + s_Wl.registry = wlDisplayGetRegistry(s_Wl.display); + wlRegistryAddListener(s_Wl.registry, &s_RegistryListener, nullptr); + s_Wl.displayRoundtrip(s_Wl.display); + + // do a roundtrip again to get remaining handles + s_Wl.displayRoundtrip(s_Wl.display); + + if (!s_Wl.compositor || !s_Wl.xdgBase || !s_Wl.shm) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + // create an input context + s_Wl.inputContext = s_Wl.xkbContextNew(XKB_CONTEXT_NO_FLAGS); + if (!s_Wl.inputContext) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + // get the current theme + s_Wl.cursorTheme = s_Wl.cursorThemeLoad(nullptr, 32, s_Wl.shm); + if (!s_Wl.cursorTheme) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + // load EGL + s_VideoEgl.handle = dlopen("libEGL.so", RTLD_LAZY); + if (s_VideoEgl.handle) { + eglGetProcAddressFn load = nullptr; + load = (eglGetProcAddressFn)dlsym(s_VideoEgl.handle, "eglGetProcAddress"); + + s_VideoEgl.initialize = (eglInitializeFn)load("eglInitialize"); + s_VideoEgl.terminate = (eglTerminateFn)load("eglTerminate"); + s_VideoEgl.getDisplay = (eglGetDisplayFn)load("eglGetDisplay"); + s_VideoEgl.chooseConfig = (eglChooseConfigFn)load("eglChooseConfig"); + s_VideoEgl.getError = (eglGetErrorFn)load("eglGetError"); + s_VideoEgl.bindAPI = (eglBindAPIFn)load("eglBindAPI"); + s_VideoEgl.getConfigs = (eglGetConfigsFn)load("eglGetConfigs"); + s_VideoEgl.getConfigAttrib = (eglGetConfigAttribFn)load("eglGetConfigAttrib"); + } + + createKeycodeTable(); + createScancodeTable(); + + s_Wl.allocator = allocator; + s_Wl.eventDriver = eventDriver; + return PAL_RESULT_SUCCESS; +} + +void wlShutdownVideo() +{ + if (s_Wl.state) { + s_Wl.xkbStateUnref(s_Wl.state); + s_Wl.xkbKeymapUnref(s_Wl.keymap); + } + + s_Wl.xkbContextUnref(s_Wl.inputContext); + if (s_Wl.compositor) { + // if compositor was found, all this will be as well + // since we check all at init + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.compositor); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.xdgBase); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.shm); + s_Wl.proxyDestroy((struct wl_proxy*)s_Wl.seat); + } + + if (!s_Wl.display) { + // opened by PAL + s_Wl.displayDisconnect(s_Wl.display); + + dlclose(s_Wl.libCursor); + dlclose(s_Wl.xkbCommon); + dlclose(s_Wl.libWaylandEgl); + dlclose(s_Wl.handle); + } + + palFree(s_Wl.allocator, s_Wl.windowData); + palFree(s_Wl.allocator, s_Wl.monitorData); + if (s_VideoEgl.handle) { + dlclose(s_VideoEgl.handle); + } + + memset(&s_Wl, 0, sizeof(Wayland)); +} + +void wlUpdateVideo() +{ + // flush pending requests + s_Mouse.tmpScrollX = 0; + s_Mouse.tmpScrollY = 0; + + // push key repeats + // we only do this if the user wants key repeat events + if (s_Keyboard.repeatKey != 0 && s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_KEYREPEAT); + if (mode != PAL_DISPATCH_MODE_NONE) { + // get now time and check with the key repeat time + uint64_t now = getCurrentTime(); + if (now >= s_Keyboard.timer) { + PalWindow* window = (PalWindow*)s_Wl.keyboardSurface; + PalKeycode key = s_Keyboard.repeatKey; + PalScancode scancode = s_Keyboard.repeatScancode; + + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_KEYREPEAT; + event.data = palPackUint32(key, scancode); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + s_Keyboard.timer += s_Keyboard.repeatRate; + } + } + } + + while (s_Wl.prepareRead(s_Wl.display) != 0) { + s_Wl.dispatchPending(s_Wl.display); + } + + s_Wl.displayFlush(s_Wl.display); + int fd = s_Wl.displayGetFd(s_Wl.display); + struct pollfd pfd = {fd, POLLIN, 0}; + if (poll(&pfd, 1, 0) > 0) { + // there are events ready to be read + s_Wl.readEvents(s_Wl.display); + + } else { + s_Wl.cancelRead(s_Wl.display); + } + + // dispatch events that were read + s_Wl.dispatchPending(s_Wl.display); +} + +PalVideoFeatures wlGetVideoFeatures() +{ + return s_Wl.features; +} + +const PalBool* wlGetKeycodeState() +{ + return s_Keyboard.keycodeState; +} + +const PalBool* wlGetScancodeState() +{ + return s_Keyboard.scancodeState; +} + +const PalBool* wlGetMouseState() +{ + return s_Mouse.state; +} + +void wlGetMouseDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.dx; + } + + if (dy) { + *dy = (float)s_Mouse.dy; + } +} + +void wlGetMouseWheelDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.WheelX; + } + + if (dy) { + *dy = (float)s_Mouse.WheelY; + } +} + +void* wlGetInstance() +{ + return (void*)s_Wl.display; +} + +// ================================================== +// Listeners +// ================================================== + +struct wl_registry_listener s_RegistryListener = { + .global = globalHandle, + .global_remove = globalRemove}; + +struct wl_output_listener s_OutputListener = + {.geometry = outputGeometry, .mode = outputMode, .done = outputDone, .scale = outputScale}; + +struct wl_output_listener s_DefaultModeListener = + {.geometry = outputGeometry, .mode = outputMode, .done = outputDone, .scale = outputScale}; + +struct wl_surface_listener s_SurfaceListener = { + .enter = surfaceHandleEnter, + .leave = surfaceHandleLeave}; + +struct wl_pointer_listener s_PointerListener = { + .enter = pointerHandleEnter, + .leave = pointerHandleLeave, + .motion = pointerHandleMotion, + .button = pointerHandleButton, + .axis = pointerHandleAxis, + .axis_discrete = pointerHandleAxisDiscrete, + .frame = pointerHandleFrame, + .axis_source = pointerHandleAxisSource, + .axis_stop = pointerHandleAxisStop}; + +struct wl_keyboard_listener s_KeyboardListener = { + .enter = keyboardHandleEnter, + .leave = keyboardHandleLeave, + .keymap = keyboardHandleRemap, + .key = keyboardHandleKey, + .repeat_info = keyboardHandleRepeatInfo, + .modifiers = keyboardHandleModifiers}; + +struct zxdg_toplevel_decoration_v1_listener s_DecorationListener = { + .configure = zxdgDecorationHandleConfigure}; + +struct wl_seat_listener s_SeatListener = { + .capabilities = seatHandleCapabilities, + .name = seatHandleName}; + +struct xdg_wm_base_listener s_WmBaseListener = {.ping = wmBaseHandlePing}; + +struct xdg_surface_listener s_XdgSurfaceListener = {.configure = xdgSurfaceHandleConfigure}; + +struct xdg_toplevel_listener s_XdgToplevelListener = { + .configure = xdgToplevelHandleConfigure, + .close = xdgToplevelHandleClose, + .configure_bounds = nullptr, + .wm_capabilities = nullptr}; + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/wayland/pal_wayland.h b/src/video/wayland/pal_wayland.h new file mode 100644 index 00000000..dec6f6a8 --- /dev/null +++ b/src/video/wayland/pal_wayland.h @@ -0,0 +1,291 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_WAYLAND_H +#define _PAL_WAYLAND_H + +#if PAL_HAS_WAYLAND_BACKEND == 1 + +#define MAX_SPAN_MONITORS 4 + +#include "pal2/pal_video.h" +#include "video/pal_video_egl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct wl_display* (*wl_display_connect_fn)(const char*); +typedef void (*wl_display_disconnect_fn)(struct wl_display*); +typedef int (*wl_display_roundtrip_fn)(struct wl_display*); +typedef int (*wl_display_dispatch_fn)(struct wl_display*); +typedef void (*wl_proxy_destroy_fn)(struct wl_proxy*); + +typedef int (*wl_proxy_add_listener_fn)( + struct wl_proxy*, + void (**)(void), + void*); + +typedef struct wl_proxy* (*wl_proxy_marshal_constructor_v_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + ...); + +typedef struct wl_proxy* (*wl_proxy_marshal_flags_fn)( + struct wl_proxy*, + uint32_t, + const struct wl_interface*, + uint32_t, + uint32_t, + ...); + +typedef uint32_t (*wl_proxy_get_version_fn)(struct wl_proxy*); + +typedef int (*wl_display_get_error_fn)(struct wl_display*); +typedef int (*wl_display_dispatch_pending_fn)(struct wl_display*); +typedef int (*wl_display_flush_fn)(struct wl_display*); +typedef int (*wl_display_prepare_read_fn)(struct wl_display*); +typedef int (*wl_display_read_events_fn)(struct wl_display*); +typedef int (*wl_display_get_fd_fn)(struct wl_display*); +typedef void (*wl_display_cancel_read_fn)(struct wl_display*); + +// xkb +typedef void (*xkb_keymap_unref_fn)(struct xkb_keymap*); +typedef struct xkb_state* (*xkb_state_new_fn)(struct xkb_keymap*); +typedef void (*xkb_state_unref_fn)(struct xkb_state*); +typedef void (*xkb_context_unref_fn)(struct xkb_context*); +typedef struct xkb_context* (*xkb_context_new_fn)(enum xkb_context_flags); +typedef uint32_t (*xkb_keysym_to_utf32_fn)(xkb_keysym_t); + +typedef xkb_keysym_t (*xkb_state_key_get_one_sym_fn)( + struct xkb_state*, + xkb_keycode_t); + +typedef struct xkb_keymap* (*xkb_keymap_new_from_string_fn)( + struct xkb_context*, + const char*, + enum xkb_keymap_format, + enum xkb_keymap_compile_flags); + +typedef enum xkb_state_component (*xkb_state_update_mask_fn)( + struct xkb_state*, + xkb_mod_mask_t, + xkb_mod_mask_t, + xkb_mod_mask_t, + xkb_layout_index_t, + xkb_layout_index_t, + xkb_layout_index_t); + +typedef int (*xkb_keymap_key_repeats_fn)( + struct xkb_keymap*, + xkb_keycode_t); + +// wayland cursor +typedef struct wl_cursor_theme* (*wl_cursor_theme_load_fn)( + const char*, + int, + struct wl_shm*); + +typedef struct wl_cursor* (*wl_cursor_theme_get_cursor_fn)( + struct wl_cursor_theme*, + const char*); + +typedef struct wl_buffer* (*wl_cursor_image_get_buffer_fn)(struct wl_cursor_image*); + +// egl_window +struct wl_egl_window; +struct wl_surface; + +typedef struct wl_egl_window* (*wl_egl_window_create_fn)( + struct wl_surface*, + int, + int); + +typedef void (*wl_egl_window_destroy_fn)(struct wl_egl_window*); + +typedef void (*wl_egl_window_resize_fn)( + struct wl_egl_window*, + int, + int, + int, + int); + +typedef struct { + struct wl_buffer* buffer; + struct wl_surface* surface; + int hotspotX; + int hotspotY; +} WaylandCursor; + +typedef struct { + void* monitor; + int dpi; +} SpanMonitor; + +typedef struct { + PalBool used; + int dpi; + int x; + int y; + uint32_t w; + uint32_t h; + uint32_t refreshRate; + uint32_t wlName; + PalOrientation orientation; + PalMonitor* monitor; + PalMonitorMode mode; // wayland only sends current + char name[32]; +} MonitorData; + +typedef struct { + PalBool skipConfigure; + PalBool skipState; + PalBool used; + PalBool isAttached; + PalBool skipIfAttached; + PalBool focused; + PalBool pushConfigureEvent; + PalBool pushStateEvent; + int x; + int y; + uint32_t w; + uint32_t h; + int dpi; + int monitorCount; + PalWindowState state; + PalWindow* window; + + struct xdg_surface* xdgSurface; + struct xdg_toplevel* xdgToplevel; + struct wl_buffer* buffer; + struct zxdg_toplevel_decoration_v1* decoration; + void* cursor; + struct wl_egl_window* eglWindow; + SpanMonitor monitors[MAX_SPAN_MONITORS]; +} WindowData; + +typedef struct { + PalBool checkFeatures; + int monitorCount; + int32_t maxWindowData; + int32_t maxMonitorData; + PalVideoFeatures features; + const PalAllocator* allocator; + PalEventDriver* eventDriver; + WindowData* windowData; + MonitorData* monitorData; + + void* handle; + void* xkbCommon; + void* libCursor; + void* libWaylandEgl; + struct wl_display* display; + struct wl_registry* registry; + struct xdg_wm_base* xdgBase; + struct wl_compositor* compositor; + struct wl_shm* shm; + struct wl_seat* seat; + struct wl_pointer* pointer; + struct wl_keyboard* keyboard; + struct zxdg_decoration_manager_v1* decorationManager; + struct zwp_pointer_constraints* pointerConstraints; + struct wl_surface* pointerSurface; + struct wl_surface* keyboardSurface; + struct wl_cursor_theme* cursorTheme; + + struct xkb_context* inputContext; + struct xkb_keymap* keymap; + struct xkb_state* state; + + const struct wl_interface* outputInterface; + const struct wl_interface* seatInterface; + const struct wl_interface* compositorInterface; + const struct wl_interface* registryInterface; + const struct wl_interface* surfaceInterface; + const struct wl_interface* shmInterface; + const struct wl_interface* bufferInterface; + const struct wl_interface* shmPoolInterface; + const struct wl_interface* regionInterface; + const struct wl_interface* pointerInterface; + const struct wl_interface* keyboardInterface; + + wl_display_connect_fn displayConnect; + wl_display_disconnect_fn displayDisconnect; + wl_display_roundtrip_fn displayRoundtrip; + wl_display_dispatch_fn displayDispatch; + wl_proxy_destroy_fn proxyDestroy; + wl_proxy_add_listener_fn proxyAddListener; + wl_proxy_marshal_constructor_v_fn proxyMarshalCnstructor; + wl_proxy_marshal_flags_fn proxyMarshalFlags; + wl_proxy_get_version_fn proxyGetVersion; + wl_display_get_error_fn getError; + wl_display_dispatch_pending_fn dispatchPending; + wl_display_flush_fn displayFlush; + wl_display_prepare_read_fn prepareRead; + wl_display_read_events_fn readEvents; + wl_display_get_fd_fn displayGetFd; + wl_display_cancel_read_fn cancelRead; + + xkb_keymap_unref_fn xkbKeymapUnref; + xkb_state_new_fn xkbStateNew; + xkb_state_unref_fn xkbStateUnref; + xkb_context_unref_fn xkbContextUnref; + xkb_context_new_fn xkbContextNew; + xkb_state_key_get_one_sym_fn xkbStateKeyGetOneSym; + xkb_keymap_new_from_string_fn xkbKeymapNewFromString; + xkb_keysym_to_utf32_fn xkbKeysymToUtf32; + xkb_state_update_mask_fn xkbStateUpdateMask; + xkb_keymap_key_repeats_fn xkbKeymapKeyRepeats; + + wl_cursor_theme_load_fn cursorThemeLoad; + wl_cursor_theme_get_cursor_fn cursorThemeGetCursor; + wl_cursor_image_get_buffer_fn cursorImageGetBuffer; + + wl_egl_window_create_fn eglWindowCreate; + wl_egl_window_destroy_fn eglWindowDestroy; + wl_egl_window_resize_fn eglWindowResize; +} Wayland; + +extern Wayland s_Wl; +extern struct wl_registry_listener s_RegistryListener; +extern struct wl_output_listener s_OutputListener; +extern struct wl_output_listener s_DefaultModeListener; + +extern struct wl_surface_listener s_SurfaceListener; +extern struct wl_pointer_listener s_PointerListener; +extern struct wl_keyboard_listener s_KeyboardListener; +extern struct zxdg_toplevel_decoration_v1_listener s_DecorationListener; + +extern struct wl_seat_listener s_SeatListener; +extern struct xdg_wm_base_listener s_WmBaseListener; +extern struct xdg_surface_listener s_XdgSurfaceListener; +extern struct xdg_toplevel_listener s_XdgToplevelListener; + +void setupProtocols(); + +struct wl_buffer* createShmBuffer( + int width, + int height, + const uint8_t* pixels, + PalBool cursor); + +MonitorData* wlGetFreeMonitorData(); +MonitorData* wlFindMonitorData(PalMonitor* monitor); +void wlFreeMonitorData(PalMonitor* monitor); +WindowData* wlGetFreeWindowData(); +WindowData* wlFindWindowData(PalWindow* window); + +#endif // PAL_HAS_WAYLAND_BACKEND +#endif // _PAL_WAYLAND_H \ No newline at end of file diff --git a/src/video/wayland/pal_wayland_protocols.c b/src/video/wayland/pal_wayland_protocols.c new file mode 100644 index 00000000..52a8d340 --- /dev/null +++ b/src/video/wayland/pal_wayland_protocols.c @@ -0,0 +1,211 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland_protocols.h" + +// ================================================== +// Xdg Shell +// ================================================== + +static const struct wl_interface* xdg_shell_types[26]; + +static const struct wl_message xdg_wm_base_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"create_positioner", "n", xdg_shell_types + 4}, + {"get_xdg_surface", "no", xdg_shell_types + 5}, + {"pong", "u", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_wm_base_events[] = { + {"ping", "u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", + 6, + 4, + xdg_wm_base_requests, + 1, + xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"set_size", "ii", xdg_shell_types + 0}, + {"set_anchor_rect", "iiii", xdg_shell_types + 0}, + {"set_anchor", "u", xdg_shell_types + 0}, + {"set_gravity", "u", xdg_shell_types + 0}, + {"set_constraint_adjustment", "u", xdg_shell_types + 0}, + {"set_offset", "ii", xdg_shell_types + 0}, + {"set_reactive", "3", xdg_shell_types + 0}, + {"set_parent_size", "3ii", xdg_shell_types + 0}, + {"set_parent_configure", "3u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", + 6, + 10, + xdg_positioner_requests, + 0, + NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"get_toplevel", "n", xdg_shell_types + 7}, + {"get_popup", "n?oo", xdg_shell_types + 8}, + {"set_window_geometry", "iiii", xdg_shell_types + 0}, + {"ack_configure", "u", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_surface_events[] = { + {"configure", "u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_surface_interface = { + "xdg_surface", + 6, + 5, + xdg_surface_requests, + 1, + xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"set_parent", "?o", xdg_shell_types + 11}, + {"set_title", "s", xdg_shell_types + 0}, + {"set_app_id", "s", xdg_shell_types + 0}, + {"show_window_menu", "ouii", xdg_shell_types + 12}, + {"move", "ou", xdg_shell_types + 16}, + {"resize", "ouu", xdg_shell_types + 18}, + {"set_max_size", "ii", xdg_shell_types + 0}, + {"set_min_size", "ii", xdg_shell_types + 0}, + {"set_maximized", "", xdg_shell_types + 0}, + {"unset_maximized", "", xdg_shell_types + 0}, + {"set_fullscreen", "?o", xdg_shell_types + 21}, + {"unset_fullscreen", "", xdg_shell_types + 0}, + {"set_minimized", "", xdg_shell_types + 0}, +}; + +static const struct wl_message xdg_toplevel_events[] = { + {"configure", "iia", xdg_shell_types + 0}, + {"close", "", xdg_shell_types + 0}, + {"configure_bounds", "4ii", xdg_shell_types + 0}, + {"wm_capabilities", "5a", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", + 6, + 14, + xdg_toplevel_requests, + 4, + xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + {"destroy", "", xdg_shell_types + 0}, + {"grab", "ou", xdg_shell_types + 22}, + {"reposition", "3ou", xdg_shell_types + 24}, +}; + +static const struct wl_message xdg_popup_events[] = { + {"configure", "iiii", xdg_shell_types + 0}, + {"popup_done", "", xdg_shell_types + 0}, + {"repositioned", "3u", xdg_shell_types + 0}, +}; + +const struct wl_interface xdg_popup_interface = { + "xdg_popup", + 6, + 3, + xdg_popup_requests, + 3, + xdg_popup_events, +}; + +// ================================================== +// Zxdg Decoration +// ================================================== + +static const struct wl_interface* xdg_decoration_unstable_v1_types[] = { + NULL, + &zxdg_toplevel_decoration_v1_interface, + &xdg_toplevel_interface, +}; + +static const struct wl_message zxdg_decoration_manager_v1_requests[] = { + {"destroy", "", xdg_decoration_unstable_v1_types + 0}, + {"get_toplevel_decoration", "no", xdg_decoration_unstable_v1_types + 1}, +}; + +const struct wl_interface zxdg_decoration_manager_v1_interface = { + "zxdg_decoration_manager_v1", + 1, + 2, + zxdg_decoration_manager_v1_requests, + 0, + NULL, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_requests[] = { + {"destroy", "", xdg_decoration_unstable_v1_types + 0}, + {"set_mode", "u", xdg_decoration_unstable_v1_types + 0}, + {"unset_mode", "", xdg_decoration_unstable_v1_types + 0}, +}; + +static const struct wl_message zxdg_toplevel_decoration_v1_events[] = { + {"configure", "u", xdg_decoration_unstable_v1_types + 0}, +}; + +const struct wl_interface zxdg_toplevel_decoration_v1_interface = { + "zxdg_toplevel_decoration_v1", + 1, + 3, + zxdg_toplevel_decoration_v1_requests, + 1, + zxdg_toplevel_decoration_v1_events, +}; + +// ================================================== +// Protocols +// ================================================== + +void setupProtocols() +{ + xdg_shell_types[0] = NULL; + xdg_shell_types[1] = NULL; + xdg_shell_types[2] = NULL; + xdg_shell_types[3] = NULL; + xdg_shell_types[4] = &xdg_positioner_interface; + xdg_shell_types[5] = &xdg_surface_interface; + xdg_shell_types[6] = s_Wl.surfaceInterface; + xdg_shell_types[7] = &xdg_toplevel_interface; + xdg_shell_types[8] = &xdg_popup_interface; + xdg_shell_types[9] = &xdg_surface_interface; + xdg_shell_types[10] = &xdg_positioner_interface; + xdg_shell_types[11] = &xdg_toplevel_interface; + xdg_shell_types[12] = s_Wl.seatInterface; + xdg_shell_types[13] = NULL; + xdg_shell_types[14] = NULL; + xdg_shell_types[15] = NULL; + xdg_shell_types[16] = s_Wl.seatInterface; + xdg_shell_types[17] = NULL; + xdg_shell_types[18] = s_Wl.seatInterface; + xdg_shell_types[19] = NULL; + xdg_shell_types[20] = NULL; + xdg_shell_types[21] = s_Wl.outputInterface; + xdg_shell_types[22] = s_Wl.seatInterface; + xdg_shell_types[23] = NULL; + xdg_shell_types[24] = &xdg_positioner_interface; + xdg_shell_types[25] = NULL; +} + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/wayland/pal_wayland_protocols.h b/src/video/wayland/pal_wayland_protocols.h new file mode 100644 index 00000000..2ffb13be --- /dev/null +++ b/src/video/wayland/pal_wayland_protocols.h @@ -0,0 +1,642 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_WAYLAND_PROTOCOLS_H +#define _PAL_WAYLAND_PROTOCOLS_H + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland.h" + +// ================================================== +// Wayland Client +// ================================================== + +static inline void* wlRegistryBind( + struct wl_registry* wl_registry, + uint32_t name, + const struct wl_interface* interface, + uint32_t version) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_registry, + WL_REGISTRY_BIND, + interface, + version, + 0, + name, + interface->name, + version, + NULL); + + return (void*)id; +} + +static inline int wlRegistryAddListener( + struct wl_registry* wl_registry, + const struct wl_registry_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_registry, (void (**)(void))listener, data); +} + +static inline struct wl_registry* wlDisplayGetRegistry(struct wl_display* wl_display) +{ + struct wl_proxy* registry; + registry = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_display, + 1, // WL_DISPLAY_GET_REGISTRY + s_Wl.registryInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_display), + 0, + NULL); + + return (struct wl_registry*)registry; +} + +static inline int wlOutputAddListener( + struct wl_output* wl_output, + const struct wl_output_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_output, (void (**)(void))listener, data); +} + +static inline struct wl_surface* wlCompositorCreateSurface(struct wl_compositor* wl_compositor) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_compositor, + 0, // WL_COMPOSITOR_CREATE_SURFACE, + s_Wl.surfaceInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_compositor), + 0, + NULL); + + return (struct wl_surface*)id; +} + +static inline void wlSurfaceCommit(struct wl_surface* wl_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_surface, + 6, // WL_SURFACE_COMMIT + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_surface), + 0); +} + +static inline void wlSurfaceDestroy(struct wl_surface* wl_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_surface, + 0, // WL_SURFACE_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_surface), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct wl_shm_pool* wlShmCreatePool( + struct wl_shm* wl_shm, + int32_t fd, + int32_t size) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_shm, + 0, // WL_SHM_CREATE_POOL + s_Wl.shmPoolInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_shm), + 0, + NULL, + fd, + size); + + return (struct wl_shm_pool*)id; +} + +static inline void wlShmPoolDestroy(struct wl_shm_pool* wl_shm_pool) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_shm_pool, + WL_SHM_POOL_DESTROY, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_shm_pool), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct wl_buffer* wlShmPoolCreateBuffer( + struct wl_shm_pool* wl_shm_pool, + int32_t offset, + int32_t width, + int32_t height, + int32_t stride, + uint32_t format) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_shm_pool, + WL_SHM_POOL_CREATE_BUFFER, + s_Wl.bufferInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_shm_pool), + 0, + NULL, + offset, + width, + height, + stride, + format); + + return (struct wl_buffer*)id; +} + +static inline void wlBufferDestroy(struct wl_buffer* wl_buffer) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_buffer, + WL_BUFFER_DESTROY, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_buffer), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void wlSurfaceAttach( + struct wl_surface* wl_surface, + struct wl_buffer* buffer, + int32_t x, + int32_t y) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_surface, + WL_SURFACE_ATTACH, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_surface), + 0, + buffer, + x, + y); +} + +static inline void wlSurfaceDamageBuffer( + struct wl_surface* wl_surface, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_surface, + WL_SURFACE_DAMAGE_BUFFER, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_surface), + 0, + x, + y, + width, + height); +} + +static inline int wlSurfaceAddListener( + struct wl_surface* wl_surface, + const struct wl_surface_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_surface, (void (**)(void))listener, data); +} + +static inline int wlSeatAddListener( + struct wl_seat* wl_seat, + const struct wl_seat_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_seat, (void (**)(void))listener, data); +} + +static inline struct wl_pointer* wlSeatGetPointer(struct wl_seat* wl_seat) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_seat, + WL_SEAT_GET_POINTER, + s_Wl.pointerInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_seat), + 0, + NULL); + + return (struct wl_pointer*)id; +} + +static inline struct wl_keyboard* wlSeatGetKeyboard(struct wl_seat* wl_seat) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_seat, + WL_SEAT_GET_KEYBOARD, + s_Wl.keyboardInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_seat), + 0, + NULL); + + return (struct wl_keyboard*)id; +} + +static inline int wlPointerAddListener( + struct wl_pointer* wl_pointer, + const struct wl_pointer_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_pointer, (void (**)(void))listener, data); +} + +static inline void wlPointerSetCursor( + struct wl_pointer* wl_pointer, + uint32_t serial, + struct wl_surface* surface, + int32_t hotspot_x, + int32_t hotspot_y) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_pointer, + WL_POINTER_SET_CURSOR, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_pointer), + 0, + serial, + surface, + hotspot_x, + hotspot_y); +} + +static inline int wlKeyboardAddListener( + struct wl_keyboard* wl_keyboard, + const struct wl_keyboard_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)wl_keyboard, (void (**)(void))listener, data); +} + +static inline struct wl_region* wlCompositorCreateRegion(struct wl_compositor* wl_compositor) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_compositor, + WL_COMPOSITOR_CREATE_REGION, + s_Wl.regionInterface, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_compositor), + 0, + NULL); + + return (struct wl_region*)id; +} + +static inline void wlRegionAdd( + struct wl_region* wl_region, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_region, + WL_REGION_ADD, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_region), + 0, + x, + y, + width, + height); +} + +static inline void wlSurfaceSetOpaqueRegion( + struct wl_surface* wl_surface, + struct wl_region* region) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_surface, + WL_SURFACE_SET_OPAQUE_REGION, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_surface), + 0, + region); +} + +static inline void wlRegionDestroy(struct wl_region* wl_region) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)wl_region, + WL_REGION_DESTROY, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)wl_region), + WL_MARSHAL_FLAG_DESTROY); +} + +// ================================================== +// Xdg +// ================================================== + +struct xdg_wm_base; +struct xdg_surface; +struct xdg_toplevel; + +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface xdg_positioner_interface; +extern const struct wl_interface xdg_surface_interface; +extern const struct wl_interface xdg_toplevel_interface; + +struct xdg_wm_base_listener { + void (*ping)( + void*, + struct xdg_wm_base*, + uint32_t); +}; + +struct xdg_surface_listener { + void (*configure)( + void*, + struct xdg_surface*, + uint32_t); +}; + +struct xdg_toplevel_listener { + // clang-format off + void (*configure)(void*, struct xdg_toplevel*, int32_t, int32_t, struct wl_array*); + void (*close)(void*, struct xdg_toplevel*); + void (*configure_bounds)(void*, struct xdg_toplevel*, int32_t, int32_t); + void (*wm_capabilities)(void*, struct xdg_toplevel*, struct wl_array*); + // clang-format on +}; + +static inline void xdgWmBasePong( + struct xdg_wm_base* xdg_wm_base, + uint32_t serial) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_wm_base, + 3, // XDG_WM_BASE_PONG + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), + 0, + serial); +} + +static inline int xdgWmBaseAddListener( + struct xdg_wm_base* xdg_wm_base, + const struct xdg_wm_base_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)xdg_wm_base, (void (**)(void))listener, data); +} + +static inline struct xdg_surface* xdgWmBaseGetXdgSurface( + struct xdg_wm_base* xdg_wm_base, + struct wl_surface* surface) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_wm_base, + 2, // XDG_WM_BASE_GET_XDG_SURFACE, + &xdg_surface_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_wm_base), + 0, + NULL, + surface); + + return (struct xdg_surface*)id; +} + +static inline struct xdg_toplevel* xdgSurfaceGetToplevel(struct xdg_surface* xdg_surface) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 1, // XDG_SURFACE_GET_TOPLEVEL, + &xdg_toplevel_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + 0, + NULL); + + return (struct xdg_toplevel*)id; +} + +static inline void xdgSurfaceAckConfigure( + struct xdg_surface* xdg_surface, + uint32_t serial) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 4, // XDG_SURFACE_ACK_CONFIGURE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + 0, + serial); +} + +static inline int xdgSurfaceAddListener( + struct xdg_surface* xdg_surface, + const struct xdg_surface_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)xdg_surface, (void (**)(void))listener, data); +} + +static inline void xdgSurfaceDestroy(struct xdg_surface* xdg_surface) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_surface, + 0, // XDG_SURFACE_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_surface), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void xdgToplevelDestroy(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 0, // XDG_TOPLEVEL_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void xdgToplevelSetTitle( + struct xdg_toplevel* xdg_toplevel, + const char* title) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 2, // XDG_TOPLEVEL_SET_TITLE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + title); +} + +static inline void xdgToplevelSetMaximized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 9, // XDG_TOPLEVEL_SET_MAXIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} + +static inline void xdgToplevelSetMinimized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 13, // XDG_TOPLEVEL_SET_MINIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} + +static inline int xdgToplevelAddListener( + struct xdg_toplevel* xdg_toplevel, + const struct xdg_toplevel_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener((struct wl_proxy*)xdg_toplevel, (void (**)(void))listener, data); +} + +static inline void xdgToplevelSetMinSize( + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 8, // XDG_TOPLEVEL_SET_MIN_SIZE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + width, + height); +} + +static inline void xdgToplevelSetMaxSize( + struct xdg_toplevel* xdg_toplevel, + int32_t width, + int32_t height) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 7, // XDG_TOPLEVEL_SET_MAX_SIZE + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + width, + height); +} + +static inline void xdgToplevelSetAppId( + struct xdg_toplevel* xdg_toplevel, + const char* app_id) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 3, // XDG_TOPLEVEL_SET_APP_ID + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0, + app_id); +} + +static inline void xdgToplevelUnsetMaximized(struct xdg_toplevel* xdg_toplevel) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)xdg_toplevel, + 10, // XDG_TOPLEVEL_UNSET_MAXIMIZED + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)xdg_toplevel), + 0); +} + +// ================================================== +// Zxdg +// ================================================== + +struct zxdg_decoration_manager_v1; +struct zxdg_toplevel_decoration_v1; + +struct zxdg_toplevel_decoration_v1_listener { + void (*configure)( + void*, + struct zxdg_toplevel_decoration_v1*, + uint32_t); +}; + +extern const struct wl_interface zxdg_decoration_manager_v1_interface; +extern const struct wl_interface zxdg_toplevel_decoration_v1_interface; + +static inline void +zxdgDecorationManagerV1Destroy(struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_decoration_manager_v1, + 0, // ZXDG_DECORATION_MANAGER_V1_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline struct zxdg_toplevel_decoration_v1* zxdgGetToplevelDecoration( + struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1, + struct xdg_toplevel* toplevel) +{ + struct wl_proxy* id; + id = s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_decoration_manager_v1, + 1, // ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, + &zxdg_toplevel_decoration_v1_interface, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_decoration_manager_v1), + 0, + NULL, + toplevel); + + return (struct zxdg_toplevel_decoration_v1*)id; +} + +static inline int zxdgToplevelDecorationV1AddListener( + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, + const struct zxdg_toplevel_decoration_v1_listener* listener, + void* data) +{ + return s_Wl.proxyAddListener( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + (void (**)(void))listener, + data); +} + +static inline void +zxdgToplevelDecorationV1Destroy(struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + 0, // ZXDG_TOPLEVEL_DECORATION_V1_DESTROY + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), + WL_MARSHAL_FLAG_DESTROY); +} + +static inline void zxdgToplevelDecorationV1SetMode( + struct zxdg_toplevel_decoration_v1* zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + s_Wl.proxyMarshalFlags( + (struct wl_proxy*)zxdg_toplevel_decoration_v1, + 1, // ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, + NULL, + s_Wl.proxyGetVersion((struct wl_proxy*)zxdg_toplevel_decoration_v1), + 0, + mode); +} + +#endif // PAL_HAS_WAYLAND_BACKEND +#endif // _PAL_WAYLAND_PROTOCOLS_H \ No newline at end of file diff --git a/src/video/wayland/pal_window_wayland.c b/src/video/wayland/pal_window_wayland.c new file mode 100644 index 00000000..830c12d7 --- /dev/null +++ b/src/video/wayland/pal_window_wayland.c @@ -0,0 +1,300 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_WAYLAND_BACKEND == 1 +#include "pal_wayland.h" +#include "pal_wayland_protocols.h" +#include +#include + +EGLConfig eglWlBackend(const int fbConfigIndex) +{ + EGLDisplay display = EGL_NO_DISPLAY; + display = s_VideoEgl.getDisplay((EGLNativeDisplayType)s_Wl.display); + if (display == EGL_NO_DISPLAY) { + return nullptr; + } + + EGLint numConfigs = 0; + if (!s_VideoEgl.getConfigs(display, nullptr, 0, &numConfigs)) { + return nullptr; + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_Wl.allocator, configSize, 0); + if (!eglConfigs) { + return nullptr; + } + + s_VideoEgl.getConfigs(display, eglConfigs, numConfigs, &numConfigs); + return eglConfigs[fbConfigIndex]; +} + +PalResult wlCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + struct wl_surface* surface = nullptr; + struct xdg_surface* xdgSurface = nullptr; + struct xdg_toplevel* xdgToplevel = nullptr; + + if (info->style & PAL_WINDOW_STYLE_TOPMOST) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + if (info->style & PAL_WINDOW_STYLE_TOOL) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { + if (!s_Wl.decorationManager) { + // user wants decorated window but its not supported + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + } + + WindowData* data = wlGetFreeWindowData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + memset(data, 0, sizeof(WindowData)); + data->used = PAL_TRUE; + data->focused = PAL_FALSE; + + // create surface + surface = wlCompositorCreateSurface(s_Wl.compositor); + if (!surface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + wlSurfaceAddListener(surface, &s_SurfaceListener, data); + xdgSurface = xdgWmBaseGetXdgSurface(s_Wl.xdgBase, surface); + if (!xdgSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + xdgToplevel = xdgSurfaceGetToplevel(xdgSurface); + if (!xdgSurface) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + // set APP id + const char* appID = info->appName; + if (!appID || strlen(appID) == 0) { + appID = "PAL"; + } + + const char* title = info->title; + if (!title) { + title = ""; + } + + xdgToplevelSetTitle(xdgToplevel, title); + xdgToplevelSetAppId(xdgToplevel, appID); + + xdgToplevelAddListener(xdgToplevel, &s_XdgToplevelListener, data); + xdgSurfaceAddListener(xdgSurface, &s_XdgSurfaceListener, data); + + // decorated window + if (!(info->style & PAL_WINDOW_STYLE_BORDERLESS)) { + struct zxdg_toplevel_decoration_v1* decoration = nullptr; + decoration = zxdgGetToplevelDecoration(s_Wl.decorationManager, xdgToplevel); + zxdgToplevelDecorationV1AddListener(decoration, &s_DecorationListener, surface); + zxdgToplevelDecorationV1SetMode(decoration, 2); + data->decoration = decoration; + } + + data->skipState = PAL_TRUE; + data->skipConfigure = PAL_TRUE; + wlSurfaceCommit(surface); + s_Wl.displayRoundtrip(s_Wl.display); + + if (info->state == PAL_WINDOW_STATE_MAXIMIZED && info->show) { + // we need the maximized size the compositor will use + // and use that to create the buffer + xdgToplevelSetMaximized(xdgToplevel); + wlSurfaceCommit(surface); + s_Wl.displayRoundtrip(s_Wl.display); + data->state = PAL_WINDOW_STATE_MAXIMIZED; + + } else { + data->w = info->width; + data->h = info->height; + } + + data->xdgSurface = xdgSurface; + data->xdgToplevel = xdgToplevel; + data->window = (PalWindow*)surface; + data->buffer = nullptr; + data->isAttached = PAL_FALSE; + data->state = PAL_WINDOW_STATE_RESTORED; + + // minimize + // This is just a requeest, the compositor might ignore it + if (info->state == PAL_WINDOW_STATE_MINIMIZED) { + xdgToplevelSetMinimized(xdgToplevel); + wlSurfaceCommit(surface); + data->state = PAL_WINDOW_STATE_MINIMIZED; + } + + // user provided a config id + if (info->fbConfigIndex) { + PalFBConfigBackend backend = info->fbConfigBackend; + EGLConfig config = nullptr; + + if (backend == PAL_FBCONFIG_BACKEND_PAL_OPENGL) { + backend = PAL_FBCONFIG_BACKEND_EGL; + } + + if (backend == PAL_FBCONFIG_BACKEND_EGL) { + config = eglWlBackend(info->fbConfigIndex); + } else { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + data->eglWindow = s_Wl.eglWindowCreate(surface, data->w, data->h); + if (!data->eglWindow) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_EGL, + s_VideoEgl.getError()); + } + + } else { + // create a white buffer for the surface + struct wl_buffer* buffer = nullptr; + buffer = createShmBuffer(data->w, data->h, nullptr, PAL_FALSE); + if (!buffer) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + wlSurfaceAttach(surface, buffer, 0, 0); + wlSurfaceDamageBuffer(surface, 0, 0, data->w, data->h); + wlSurfaceCommit(surface); + data->buffer = buffer; + } + + struct wl_region* region = wlCompositorCreateRegion(s_Wl.compositor); + if (region) { + wlRegionAdd(region, 0, 0, data->w, data->h); + wlSurfaceSetOpaqueRegion(surface, region); + wlRegionDestroy(region); + wlSurfaceCommit(surface); + } + + s_Wl.displayRoundtrip(s_Wl.display); + if (!s_Wl.decorationManager && s_Wl.eventDriver) { + PalEventDriver* driver = s_Wl.eventDriver; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + PalEventType type = PAL_EVENT_TYPE_WINDOW_DECORATION_MODE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = PAL_DECORATION_MODE_CLIENT_SIDE; + event.data2 = palPackPointer(data->window); + palPushEvent(driver, &event); + } + } + + // Since wayland does not have a way to set unique data + // to a surface without taking control from users + // we might implement a simple hash map to do that + // but at the moment a linear search is fine + data->skipState = PAL_FALSE; + data->skipConfigure = PAL_FALSE; + *outWindow = data->window; + return PAL_RESULT_SUCCESS; +} + +void wlDestroyWindow(PalWindow* window) +{ + WindowData* data = wlFindWindowData(window); + if (!data || (data && data->isAttached)) { + return; + } + + if (data->decoration) { + zxdgToplevelDecorationV1Destroy(data->decoration); + } + + if (data->eglWindow) { + s_Wl.eglWindowDestroy(data->eglWindow); + } else { + wlBufferDestroy(data->buffer); + } + + xdgToplevelDestroy(data->xdgToplevel); + xdgSurfaceDestroy(data->xdgSurface); + wlSurfaceDestroy((struct wl_surface*)window); + s_Wl.displayFlush(s_Wl.display); + data->used = PAL_FALSE; +} + +void wlMinimizeWindow(PalWindow* window) +{ + WindowData* data = wlFindWindowData(window); + xdgToplevelSetMinimized(data->xdgToplevel); +} + +void wlMaximizeWindow(PalWindow* window) +{ + WindowData* data = wlFindWindowData(window); + xdgToplevelSetMaximized(data->xdgToplevel); +} + +void wlRestoreWindow(PalWindow* window) +{ + WindowData* data = wlFindWindowData(window); + // we can only restore from a maximized state + xdgToplevelUnsetMaximized(data->xdgToplevel); +} + +PalBool wlIsWindowVisible(PalWindow* window) +{ + return PAL_FALSE; +} + +void wlGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info) +{ + WindowData* data = wlFindWindowData(window); + info->nativeInstance = (void*)s_Wl.display; + info->nativeWindow = (void*)window; + info->nativeHandle1 = data->xdgSurface; + info->nativeHandle2 = data->xdgToplevel; + info->nativeHandle3 = data->eglWindow; +} + +void wlSetWindowTitle( + PalWindow* window, + const char* title) +{ + WindowData* data = wlFindWindowData(window); + xdgToplevelSetTitle(data->xdgToplevel, title); + s_Wl.displayFlush(s_Wl.display); +} + +void wlSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height) +{ + WindowData* data = wlFindWindowData(window); + xdgToplevelSetMinSize(data->xdgToplevel, width, height); + xdgToplevelSetMaxSize(data->xdgToplevel, width, height); + wlSurfaceCommit((struct wl_surface*)window); +} + +#endif // PAL_HAS_WAYLAND_BACKEND \ No newline at end of file diff --git a/src/video/win32/pal_cursor_win32.c b/src/video/win32/pal_cursor_win32.c new file mode 100644 index 00000000..382dba36 --- /dev/null +++ b/src/video/win32/pal_cursor_win32.c @@ -0,0 +1,207 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_video_win32.h" + +PalResult win32CreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + // describe the icon pixels + BITMAPV5HEADER bitInfo = {0}; + bitInfo.bV5Size = sizeof(BITMAPV5HEADER); + bitInfo.bV5Width = info->width; + bitInfo.bV5Height = -(int32_t)info->height; // this is topdown by default + + // default parameters + bitInfo.bV5Planes = 1; + bitInfo.bV5BitCount = 32; // PAL supports 32 bits + bitInfo.bV5Compression = BI_BITFIELDS; + bitInfo.bV5RedMask = 0x00FF0000; + bitInfo.bV5GreenMask = 0x0000FF00; + bitInfo.bV5BlueMask = 0x000000FF; + bitInfo.bV5AlphaMask = 0xFF000000; + + HDC hdc = GetDC(nullptr); + void* dibPixels = nullptr; + + // create dib section + // clang-format off + HBITMAP bitmap = s_Win32.createDIBSection( + hdc, + (BITMAPINFO*)&bitInfo, + DIB_RGB_COLORS, + &dibPixels, + nullptr, + 0); + // clang-format on + + if (!bitmap) { + ReleaseDC(nullptr, hdc); + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + ReleaseDC(nullptr, hdc); + + // convert RGBA to BGRA + uint8_t* pixels = (uint8_t*)dibPixels; + for (uint32_t i = 0; i < info->width * info->height; i++) { + uint8_t r = info->pixels[i * 4 + 0]; // Red + uint8_t g = info->pixels[i * 4 + 1]; // Green + uint8_t b = info->pixels[i * 4 + 2]; // Blue + uint8_t a = info->pixels[i * 4 + 3]; // Alpha + + // premultiply only if alpha is not 0 + if (a == 0) { + r = g = b = 0; + } else { + r = (uint8_t)((r * a) / 255); + g = (uint8_t)((g * a) / 255); + b = (uint8_t)((b * a) / 255); + } + + pixels[i * 4 + 0] = b; + pixels[i * 4 + 1] = g; + pixels[i * 4 + 2] = r; + pixels[i * 4 + 3] = a; + } + + ICONINFO iconInfo = {0}; + iconInfo.fIcon = FALSE; + iconInfo.hbmColor = bitmap; + iconInfo.hbmMask = bitmap; + iconInfo.xHotspot = info->xHotspot; + iconInfo.xHotspot = info->yHotspot; + + // create the cursor with the iconinfo + HCURSOR cursor = CreateIconIndirect(&iconInfo); + if (!cursor) { + s_Win32.deleteObject(bitmap); + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + s_Win32.deleteObject(bitmap); + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} + +PalResult win32CreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + HCURSOR cursor = nullptr; + switch (type) { + case PAL_CURSOR_TYPE_ARROW: { + cursor = LoadCursorW(nullptr, IDC_ARROW); + break; + } + + case PAL_CURSOR_TYPE_HAND: { + cursor = LoadCursorW(nullptr, IDC_HAND); + break; + } + + case PAL_CURSOR_TYPE_CROSS: { + cursor = LoadCursorW(nullptr, IDC_CROSS); + break; + } + + case PAL_CURSOR_TYPE_IBEAM: { + cursor = LoadCursorW(nullptr, IDC_IBEAM); + break; + } + + case PAL_CURSOR_TYPE_WAIT: { + cursor = LoadCursorW(nullptr, IDC_WAIT); + break; + } + } + + if (!cursor) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + *outCursor = (PalCursor*)cursor; + return PAL_RESULT_SUCCESS; +} + +void win32DestroyCursor(PalCursor* cursor) +{ + DestroyCursor((HCURSOR)cursor); +} + +void win32ShowCursor(PalBool show) +{ + ShowCursor(show); +} + +void win32ClipCursor( + PalWindow* window, + PalBool clip) +{ + if (clip) { + RECT rect; + GetClientRect((HWND)window, &rect); + POINT tmp = {rect.left, rect.top}; + POINT tmp2 = {rect.right, rect.bottom}; + + ClientToScreen((HWND)window, &tmp); + ClientToScreen((HWND)window, &tmp2); + + RECT clipRect = {tmp.x, tmp.y, tmp2.x, tmp2.y}; + ClipCursor(&clipRect); + + } else { + ClipCursor(nullptr); + } +} + +void win32GetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + POINT pos; + GetCursorPos(&pos); + ScreenToClient((HWND)window, &pos); + if (x) { + *x = pos.x; + } + + if (y) { + *y = pos.y; + } +} + +void win32SetCursorPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + POINT pos = {x, y}; + ClientToScreen((HWND)window, &pos); + SetCursorPos(pos.x, pos.y); +} + +void win32SetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + data->cursor = (HCURSOR)cursor; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/video/win32/pal_icon_win32.c b/src/video/win32/pal_icon_win32.c new file mode 100644 index 00000000..d2d86083 --- /dev/null +++ b/src/video/win32/pal_icon_win32.c @@ -0,0 +1,109 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_video_win32.h" + +PalResult win32CreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + // describe the icon pixels + BITMAPV5HEADER bitInfo = {0}; + bitInfo.bV5Size = sizeof(BITMAPV5HEADER); + bitInfo.bV5Width = info->width; + bitInfo.bV5Height = -(int32_t)info->height; // this is topdown by default + + // default parameters + bitInfo.bV5Planes = 1; + bitInfo.bV5BitCount = 32; // PAL supports 32 bits + bitInfo.bV5Compression = BI_BITFIELDS; + bitInfo.bV5RedMask = 0x00FF0000; + bitInfo.bV5GreenMask = 0x0000FF00; + bitInfo.bV5BlueMask = 0x000000FF; + bitInfo.bV5AlphaMask = 0xFF000000; + + HDC hdc = GetDC(nullptr); + void* dibPixels = nullptr; + + // create dib section + // clang-format off + HBITMAP bitmap = s_Win32.createDIBSection( + hdc, + (BITMAPINFO*)&bitInfo, + DIB_RGB_COLORS, + &dibPixels, + nullptr, + 0); + // clang-format on + + if (!bitmap) { + ReleaseDC(nullptr, hdc); + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + ReleaseDC(nullptr, hdc); + + // convert RGBA to BGRA + uint8_t* pixels = (uint8_t*)dibPixels; + for (uint32_t i = 0; i < info->width * info->height; i++) { + uint8_t r = info->pixels[i * 4 + 0]; // Red + uint8_t g = info->pixels[i * 4 + 1]; // Green + uint8_t b = info->pixels[i * 4 + 2]; // Blue + uint8_t a = info->pixels[i * 4 + 3]; // Alpha + + // premultiply only if alpha is not 0 + if (a == 0) { + r = g = b = 0; + } else { + r = (uint8_t)((r * a) / 255); + g = (uint8_t)((g * a) / 255); + b = (uint8_t)((b * a) / 255); + } + + pixels[i * 4 + 0] = b; + pixels[i * 4 + 1] = g; + pixels[i * 4 + 2] = r; + pixels[i * 4 + 3] = a; + } + + ICONINFO iconInfo = {0}; + iconInfo.fIcon = TRUE; + iconInfo.hbmMask = bitmap; + iconInfo.hbmColor = bitmap; + + // create the icon with the icon info + HICON icon = CreateIconIndirect(&iconInfo); + if (!icon) { + s_Win32.deleteObject(bitmap); + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + s_Win32.deleteObject(bitmap); + *outIcon = (PalIcon*)icon; + return PAL_RESULT_SUCCESS; +} + +void win32DestroyIcon(PalIcon* icon) +{ + DestroyIcon((HICON)icon); +} + +void win32SetWindowIcon( + PalWindow* window, + PalIcon* icon) +{ + SendMessageW((HWND)window, WM_SETICON, ICON_BIG, (LPARAM)icon); + SendMessageW((HWND)window, WM_SETICON, ICON_SMALL, (LPARAM)icon); +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/video/win32/pal_monitor_win32.c b/src/video/win32/pal_monitor_win32.c new file mode 100644 index 00000000..a63d7d5a --- /dev/null +++ b/src/video/win32/pal_monitor_win32.c @@ -0,0 +1,339 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_video_win32.h" + +#define MONITOR_DPI 0 +#define MAX_MODE_COUNT 128 + +typedef struct { + int32_t count; + int32_t maxCount; + PalMonitor** monitors; +} MonitorData; + +BOOL CALLBACK enumMonitors( + HMONITOR monitor, + HDC hdc, + LPRECT lRect, + LPARAM lParam) +{ + MonitorData* data = (MonitorData*)lParam; + if (data->monitors) { + if (data->count < data->maxCount) { + data->monitors[data->count] = (PalMonitor*)monitor; + } + } + + data->count++; + return PAL_TRUE; +} + +static inline PalOrientation orientationFromWin32(DWORD orientation) +{ + switch (orientation) { + case DMDO_DEFAULT: + return PAL_ORIENTATION_LANDSCAPE; + + case DMDO_90: + return PAL_ORIENTATION_PORTRAIT; + + case DMDO_180: + return PAL_ORIENTATION_LANDSCAPE_FLIPPED; + + case DMDO_270: + return PAL_ORIENTATION_PORTRAIT_FLIPPED; + } + return PAL_ORIENTATION_LANDSCAPE; +} + +static inline DWORD orientationToin32(PalOrientation orientation) +{ + switch (orientation) { + case PAL_ORIENTATION_LANDSCAPE: + return DMDO_DEFAULT; + + case PAL_ORIENTATION_PORTRAIT: + return DMDO_90; + + case PAL_ORIENTATION_LANDSCAPE_FLIPPED: + return DMDO_180; + + case PAL_ORIENTATION_PORTRAIT_FLIPPED: + return DMDO_270; + } + return 0; +} + +static inline PalResult setMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode, + PalBool test) +{ + MONITORINFOEXW mi = {0}; + mi.cbSize = sizeof(MONITORINFOEXW); + if (!GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi)) { + DWORD error = GetLastError(); + if (error == ERROR_INVALID_HANDLE) { + return palMakeResult(PAL_RESULT_CODE_INVALID_HANDLE, PAL_RESULT_SOURCE_WIN32, error); + + } else { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_WIN32, error); + } + } + + DWORD flags = DM_PELSWIDTH | DM_PELSHEIGHT; + flags |= DM_DISPLAYFREQUENCY | DM_BITSPERPEL; + + DEVMODE devMode = {0}; + devMode.dmSize = sizeof(DEVMODE); + devMode.dmFields = flags; + + devMode.dmPelsWidth = mode->width; + devMode.dmPelsHeight = mode->height; + devMode.dmDisplayFrequency = mode->refreshRate; + devMode.dmBitsPerPel = mode->bpp; + + DWORD settingsFlag = CDS_FULLSCREEN; + if (test) { + settingsFlag = CDS_TEST; + } + + ULONG result = ChangeDisplaySettingsExW(mi.szDevice, &devMode, NULL, settingsFlag, NULL); + if (result == DISP_CHANGE_SUCCESSFUL) { + return PAL_RESULT_SUCCESS; + + } else { + return palMakeResult( + PAL_RESULT_CODE_INVALID_HANDLE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } +} + +static inline PalBool compareMonitorMode( + const PalMonitorMode* a, + const PalMonitorMode* b) +{ + + // clang-format off + return a->bpp == b->bpp && + a->width == b->width && + a->height == b->height && + a->refreshRate == b->refreshRate; + // clang-format on +} + +static inline void addMonitorMode( + PalMonitorMode* modes, + const PalMonitorMode* mode, + uint32_t* count) +{ + // check if we have a duplicate mode + for (int32_t i = 0; i < *count; i++) { + PalMonitorMode* oldMode = &modes[i]; + if (compareMonitorMode(oldMode, mode)) { + return; // discard it + } + } + + // new mode + modes[*count] = *mode; + *count += 1; +} + +PalResult win32EnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitors) +{ + MonitorData data; + data.count = 0; + data.monitors = outMonitors; + data.maxCount = outMonitors ? *count : 0; + EnumDisplayMonitors(nullptr, nullptr, enumMonitors, (LPARAM)&data); + + if (!outMonitors) { + *count = data.count; + } + return PAL_RESULT_SUCCESS; +} + +void win32GetPrimaryMonitor(PalMonitor** outMonitor) +{ + HMONITOR monitor = MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); + *outMonitor = (PalMonitor*)monitor; +} + +void win32GetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + MONITORINFOEXW mi = {0}; + mi.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi); + info->x = mi.rcMonitor.left; + info->y = mi.rcMonitor.top; + info->width = mi.rcMonitor.right - mi.rcMonitor.left; + info->height = mi.rcMonitor.bottom - mi.rcWork.top; + + // get name + WideCharToMultiByte(CP_UTF8, 0, mi.szDevice, -1, info->name, 32, NULL, NULL); + + DEVMODE devMode = {0}; + devMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); + info->refreshRate = devMode.dmDisplayFrequency; + info->orientation = orientationFromWin32(devMode.dmDisplayOrientation); + + // get dpi scale + UINT dpiX, dpiY; + if (s_Win32.getDpiForMonitor) { + s_Win32.getDpiForMonitor((HMONITOR)monitor, MONITOR_DPI, &dpiX, &dpiY); + + } else { + dpiX = 96; + } + info->dpi = dpiX; + + // check for primary monitor + HMONITOR primary = nullptr; + primary = MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); + if (!primary) { + info->primary = PAL_FALSE; + } + + if (primary == (HMONITOR)monitor) { + info->primary = PAL_TRUE; + } +} + +void win32EnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* modes) +{ + uint32_t modeCount = 0; + int32_t maxModes = 0; + PalMonitorMode* monitorModes = nullptr; + + MONITORINFOEXW mi = {0}; + mi.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi); + if (!modes) { + // allocate and store tmp monitor modesand check for the interested + // fields. + monitorModes = palAllocate(s_Win32.allocator, sizeof(PalMonitorMode) * MAX_MODE_COUNT, 0); + if (!monitorModes) { + return; + } + + memset(monitorModes, 0, sizeof(PalMonitorMode) * MAX_MODE_COUNT); + maxModes = MAX_MODE_COUNT; + + } else { + monitorModes = modes; + maxModes = *count; + } + + DEVMODEW dm = {0}; + dm.dmSize = sizeof(DEVMODE); + for (int32_t i = 0; EnumDisplaySettingsW(mi.szDevice, i, &dm); i++) { + // Pal support up to 128 modes + if (modeCount > maxModes) { + break; + } + + PalMonitorMode* mode = &monitorModes[modeCount]; + mode->refreshRate = dm.dmDisplayFrequency; + mode->width = dm.dmPelsWidth; + mode->height = dm.dmPelsHeight; + mode->bpp = dm.dmBitsPerPel; + addMonitorMode(monitorModes, mode, &modeCount); + } + + if (!modes) { + *count = modeCount; + palFree(s_Win32.allocator, monitorModes); + } +} + +void win32GetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + MONITORINFOEXW mi = {0}; + mi.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi); + + DEVMODE devMode = {0}; + devMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); + mode->width = devMode.dmPelsWidth; + mode->height = devMode.dmPelsHeight; + mode->refreshRate = devMode.dmDisplayFrequency; + mode->bpp = devMode.dmBitsPerPel; +} + +PalResult win32SetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return setMonitorMode(monitor, mode, PAL_FALSE); +} + +PalResult win32ValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return setMonitorMode(monitor, mode, PAL_TRUE); +} + +PalResult win32SetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + DWORD win32Orientation = orientationToin32(orientation); + MONITORINFOEXW mi = {0}; + mi.cbSize = sizeof(MONITORINFOEXW); + GetMonitorInfoW((HMONITOR)monitor, (MONITORINFO*)&mi); + + DEVMODE devMode = {0}; + devMode.dmSize = sizeof(DEVMODE); + EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &devMode); + DWORD monitorOrientation = devMode.dmDisplayOrientation; + + // clang-format off + // only swap size if switching between landscape and portrait + PalBool isMonitorLandscape = (monitorOrientation == DMDO_DEFAULT || + monitorOrientation == DMDO_180); + + PalBool isLandscape = (win32Orientation == DMDO_DEFAULT || + win32Orientation == DMDO_180); + // clang-format on + + if (isMonitorLandscape != isLandscape) { + DWORD tmp = devMode.dmPelsWidth; + devMode.dmPelsWidth = devMode.dmPelsHeight; + devMode.dmPelsHeight = tmp; + } + + devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYORIENTATION; + devMode.dmDisplayOrientation = win32Orientation; + ULONG result = ChangeDisplaySettingsExW(mi.szDevice, &devMode, NULL, CDS_RESET, NULL); + if (result == DISP_CHANGE_SUCCESSFUL) { + return PAL_RESULT_SUCCESS; + + } else { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/video/win32/pal_video_win32.c b/src/video/win32/pal_video_win32.c new file mode 100644 index 00000000..3c61d19c --- /dev/null +++ b/src/video/win32/pal_video_win32.c @@ -0,0 +1,1039 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_video_win32.h" +#include + +#define PROCESS_DPI_AWARE 2 +#define NULL_BUTTON_SERIAL 0xffffffffU + +typedef struct { + PalBool pendingResize; + PalBool pendingMove; + PalBool pendingState; + uint32_t width; + uint32_t height; + int32_t x; + int32_t y; + PalWindowState state; + PalWindow* window; +} PendingEvent; + +typedef struct { + int32_t pendingHighSurrogate; + PalBool scancodeState[PAL_SCANCODE_COUNT]; + PalBool keycodeState[PAL_KEYCODE_COUNT]; + int scancodes[512]; + int keycodes[256]; +} Keyboard; + +typedef struct { + PalBool pushMouseDelta; + int32_t dx; + int32_t dy; + int32_t WheelX; + int32_t WheelY; + PalBool state[PAL_MOUSE_BUTTON_COUNT]; +} Mouse; + +static PendingEvent s_Event = {0}; +static BYTE s_RawBuffer[4096] = {0}; +static Mouse s_Mouse = {0}; +static Keyboard s_Keyboard = {0}; +VideoWin32 s_Win32 = {0}; + +LRESULT CALLBACK videoProc( + HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) +{ + // check if the window has been created + WindowData* data = (WindowData*)GetPropW(hwnd, PAL_VIDEO_PROP); + if (!data) { + // window has not been created yet + return DefWindowProcW(hwnd, msg, wParam, lParam); + } + + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + switch (msg) { + case WM_CLOSE: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_CLOSE); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_CLOSE; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_SIZE: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_SIZE); + uint32_t width = (uint32_t)LOWORD(lParam); + uint32_t height = (uint32_t)HIWORD(lParam); + + if (mode == PAL_DISPATCH_MODE_CALLBACK) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_SIZE; + event.data = palPackUint32(width, height); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + + } else if (mode == PAL_DISPATCH_MODE_POLL) { + s_Event.pendingResize = PAL_TRUE; + s_Event.width = width; + s_Event.height = height; + s_Event.window = (PalWindow*)hwnd; + } + + // trigger state event + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_STATE); + PalWindowState state = PAL_WINDOW_STATE_RESTORED; + if (mode == PAL_DISPATCH_MODE_NONE) { + return 0; + } + + switch (wParam) { + case SIZE_MINIMIZED: { + state = PAL_WINDOW_STATE_MINIMIZED; + break; + } + + case SIZE_MAXIMIZED: { + state = PAL_WINDOW_STATE_MAXIMIZED; + break; + } + } + + // if state has not changed, we discard the event + if (data->state == state) { + return 0; + } + + if (mode == PAL_DISPATCH_MODE_CALLBACK) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_STATE; + event.data = state; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + + } else if (mode == PAL_DISPATCH_MODE_POLL) { + s_Event.pendingState = PAL_TRUE; + s_Event.state = state; + } + data->state = state; + } + + return 0; + } + + case WM_MOVE: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_MOVE); + int32_t x = GET_X_LPARAM(lParam); + int32_t y = GET_Y_LPARAM(lParam); + + if (mode == PAL_DISPATCH_MODE_CALLBACK) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_MOVE; + event.data = palPackInt32(x, y); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + + } else { + s_Event.pendingMove = PAL_TRUE; + s_Event.x = x; + s_Event.y = y; + s_Event.window = (PalWindow*)hwnd; + } + } + + return 0; + } + + case WM_SHOWWINDOW: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_VISIBILITY; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = (PalBool)wParam; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_SETFOCUS: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_FOCUS); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_FOCUS; + event.data = PAL_TRUE; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_KILLFOCUS: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_WINDOW_FOCUS); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_FOCUS; + event.data = PAL_FALSE; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_ENTERSIZEMOVE: { + s_Mouse.pushMouseDelta = PAL_FALSE; + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_MODAL_BEGIN; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_EXITSIZEMOVE: { + s_Mouse.pushMouseDelta = PAL_TRUE; + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_MODAL_END; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_DPICHANGED: { + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MONITOR_DPI_CHANGED; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = HIWORD(wParam); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_DEVICECHANGE: { + // check if the monitors list has been changed + if (wParam != 0x0007) { + return 0; + } + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MONITOR_LIST_CHANGED; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_MOUSEHWHEEL: { + int32_t delta = GET_WHEEL_DELTA_WPARAM(wParam); + s_Mouse.WheelX = delta / WHEEL_DELTA; + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_MOUSE_WHEEL); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_MOUSE_WHEEL; + event.data = palPackFloat((float)s_Mouse.WheelX, 0); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_MOUSEWHEEL: { + int32_t delta = GET_WHEEL_DELTA_WPARAM(wParam); + s_Mouse.WheelY = delta / WHEEL_DELTA; + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_MOUSE_WHEEL); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_MOUSE_WHEEL; + event.data = palPackFloat(0, (float)s_Mouse.WheelY); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_MOUSEMOVE: { + const int32_t x = GET_X_LPARAM(lParam); + const int32_t y = GET_Y_LPARAM(lParam); + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_MOUSE_MOVE); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_MOUSE_MOVE; + event.data = palPackInt32(x, y); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_INPUT: { + UINT size = sizeof(s_RawBuffer); + UINT result = GetRawInputData( + (HRAWINPUT)lParam, + RID_INPUT, + s_RawBuffer, + &size, + sizeof(RAWINPUTHEADER)); + + if (result == (UINT)-1) { + break; + } + + RAWINPUT* raw = (RAWINPUT*)s_RawBuffer; + RAWMOUSE* mouse = &raw->data.mouse; + // push only if we are not rresizing or moving with the mouse + if (s_Mouse.pushMouseDelta && (mouse->lLastX || mouse->lLastY)) { + s_Mouse.dx += mouse->lLastX; + s_Mouse.dy += mouse->lLastY; + + float dx = (float)s_Mouse.dx; + float dy = (float)s_Mouse.dy; + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MOUSE_DELTA; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackFloat(dx, dy); + palPushEvent(driver, &event); + } + } + } + break; + } + + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: + case WM_XBUTTONUP: { + PalMouseButton button = PAL_MOUSE_BUTTON_UNKNOWN; + PalEventType type; + PalBool pressed = PAL_FALSE; + + if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) { + button = PAL_MOUSE_BUTTON_LEFT; + + } else if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP) { + button = PAL_MOUSE_BUTTON_RIGHT; + + } else if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONUP) { + button = PAL_MOUSE_BUTTON_MIDDLE; + + } else if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONUP) { + // check which x buttton + WORD xButton = HIWORD(wParam); + if (xButton == XBUTTON1) { + button = PAL_MOUSE_BUTTON_X1; + + } else if (xButton == XBUTTON2) { + button = PAL_MOUSE_BUTTON_X2; + } + } + + // clang-format off + // check if we pressed or released the button + if (msg == WM_LBUTTONDOWN || + msg == WM_RBUTTONDOWN || + msg == WM_MBUTTONDOWN || + msg == WM_XBUTTONDOWN) { + pressed = PAL_TRUE; + type = PAL_EVENT_TYPE_MOUSE_BUTTONDOWN; + + } else { + pressed = PAL_FALSE; + type = PAL_EVENT_TYPE_MOUSE_BUTTONUP; + } + // clang-format on + + // set mouse capture + if (msg == WM_LBUTTONDOWN) { + SetCapture(hwnd); + + } else if (msg == WM_LBUTTONUP) { + ReleaseCapture(); + } + + s_Mouse.state[button] = pressed; + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(button, NULL_BUTTON_SERIAL); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: { + PalKeycode keycode = PAL_KEYCODE_UNKNOWN; + PalScancode scancode = PAL_SCANCODE_UNKNOWN; + PalEventType type; + int32_t win32Keycode; + int32_t win32Scancode; + PalBool pressed = PAL_FALSE; + PalBool extended = PAL_FALSE; + + pressed = (HIWORD(lParam) & KF_UP) ? PAL_FALSE : PAL_TRUE; + extended = (lParam >> 24) & 1; // we use this for special keys + win32Scancode = (HIWORD(lParam) & (KF_EXTENDED | 0xff)); + win32Keycode = (UINT)wParam; + + // spcecial scancode handling + if (!extended && win32Scancode == 0x045) { + scancode = PAL_SCANCODE_NUMLOCK; + + } else { + uint16_t index = win32Scancode | (extended << 8); + scancode = s_Keyboard.scancodes[index]; + } + + keycode = s_Keyboard.keycodes[win32Keycode]; + if (keycode == PAL_KEYCODE_UNKNOWN) { + // we didnt get any printable key + // we use the scancode + // Since PalKeycode and PalScancode have the same integers + // we can make a direct cast without a table + // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) + keycode = (PalKeycode)(uint32_t)scancode; + } + + if (win32Keycode == VK_SNAPSHOT) { + // printscreen since the platform does not get us a keydown, we + // do that ourselves + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_KEYDOWN); + keycode = PAL_KEYCODE_PRINTSCREEN; + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_KEYDOWN; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + s_Keyboard.keycodeState[keycode] = PAL_TRUE; + } + } + + // check before updating state + PalBool repeat = s_Keyboard.keycodeState[keycode]; + if (pressed) { + s_Keyboard.keycodeState[keycode] = PAL_TRUE; + s_Keyboard.scancodeState[scancode] = PAL_TRUE; + + type = PAL_EVENT_TYPE_KEYDOWN; + if (repeat) { + type = PAL_EVENT_TYPE_KEYREPEAT; + } + + } else { + s_Keyboard.keycodeState[keycode] = PAL_FALSE; + s_Keyboard.scancodeState[scancode] = PAL_FALSE; + type = PAL_EVENT_TYPE_KEYUP; + } + + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(driver, &event); + } + } + return 0; + } + + case WM_ERASEBKGND: { + return PAL_TRUE; + } + + case WM_SETCURSOR: { + if (LOWORD(lParam) == HTCLIENT) { + if (data && data->cursor) { + SetCursor(data->cursor); + } else { + // no cursor, use default + SetCursor(s_Win32.defaultCursor); + } + return PAL_TRUE; + } + break; + } + + case WM_CHAR: { + PalEventType type = PAL_EVENT_TYPE_KEYCHAR; + uint32_t codepoint = 0; + if (s_Win32.eventDriver) { + PalEventDriver* driver = s_Win32.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_MODE_NONE) { + break; + } + } + // Most characters comes as two WM_CHAR messags or event + // we store the first one and combine with the second if we got any + uint16_t character = (uint16_t)wParam; + if (character >= 0xD800 && character <= 0xDBFF) { + // high surrogate + s_Keyboard.pendingHighSurrogate = character; + } else if (character >= 0xDC00 && character <= 0xDFFF) { + if (s_Keyboard.pendingHighSurrogate) { + // low surrogate we combine both + uint32_t high = s_Keyboard.pendingHighSurrogate - 0xD800; + uint32_t low = character - 0xDC00; + codepoint = 0x10000 + ((high << 10) | low); + s_Keyboard.pendingHighSurrogate = 0; + } + + } else { + // normal character (A-Z) + codepoint = character; + } + + // push an event + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer((PalWindow*)hwnd); + palPushEvent(s_Win32.eventDriver, &event); + } + } + + return DefWindowProcW(hwnd, msg, wParam, lParam); +} + +static void createKeycodeTable() +{ + // Tis is for only printable and text input keys + + // Letters + s_Keyboard.keycodes['A'] = PAL_KEYCODE_A; + s_Keyboard.keycodes['B'] = PAL_KEYCODE_B; + s_Keyboard.keycodes['C'] = PAL_KEYCODE_C; + s_Keyboard.keycodes['D'] = PAL_KEYCODE_D; + s_Keyboard.keycodes['E'] = PAL_KEYCODE_E; + s_Keyboard.keycodes['F'] = PAL_KEYCODE_F; + s_Keyboard.keycodes['G'] = PAL_KEYCODE_G; + s_Keyboard.keycodes['H'] = PAL_KEYCODE_H; + s_Keyboard.keycodes['I'] = PAL_KEYCODE_I; + s_Keyboard.keycodes['J'] = PAL_KEYCODE_J; + s_Keyboard.keycodes['K'] = PAL_KEYCODE_K; + s_Keyboard.keycodes['L'] = PAL_KEYCODE_L; + s_Keyboard.keycodes['M'] = PAL_KEYCODE_M; + s_Keyboard.keycodes['N'] = PAL_KEYCODE_N; + s_Keyboard.keycodes['O'] = PAL_KEYCODE_O; + s_Keyboard.keycodes['P'] = PAL_KEYCODE_P; + s_Keyboard.keycodes['Q'] = PAL_KEYCODE_Q; + s_Keyboard.keycodes['R'] = PAL_KEYCODE_R; + s_Keyboard.keycodes['S'] = PAL_KEYCODE_S; + s_Keyboard.keycodes['T'] = PAL_KEYCODE_T; + s_Keyboard.keycodes['U'] = PAL_KEYCODE_U; + s_Keyboard.keycodes['V'] = PAL_KEYCODE_V; + s_Keyboard.keycodes['W'] = PAL_KEYCODE_W; + s_Keyboard.keycodes['X'] = PAL_KEYCODE_X; + s_Keyboard.keycodes['Y'] = PAL_KEYCODE_Y; + s_Keyboard.keycodes['Z'] = PAL_KEYCODE_Z; + + // Control + s_Keyboard.keycodes[VK_SPACE] = PAL_KEYCODE_SPACE; + + // Misc + s_Keyboard.keycodes[VK_OEM_7] = PAL_KEYCODE_APOSTROPHE; + s_Keyboard.keycodes[VK_OEM_5] = PAL_KEYCODE_BACKSLASH; + s_Keyboard.keycodes[VK_OEM_COMMA] = PAL_KEYCODE_COMMA; + s_Keyboard.keycodes[VK_OEM_PLUS] = PAL_KEYCODE_EQUAL; + s_Keyboard.keycodes[VK_OEM_3] = PAL_KEYCODE_GRAVEACCENT; + s_Keyboard.keycodes[VK_OEM_MINUS] = PAL_KEYCODE_SUBTRACT; + s_Keyboard.keycodes[VK_OEM_PERIOD] = PAL_KEYCODE_PERIOD; + s_Keyboard.keycodes[VK_OEM_1] = PAL_KEYCODE_SEMICOLON; + s_Keyboard.keycodes[VK_OEM_2] = PAL_KEYCODE_SLASH; + s_Keyboard.keycodes[VK_OEM_4] = PAL_KEYCODE_LBRACKET; + s_Keyboard.keycodes[VK_OEM_6] = PAL_KEYCODE_RBRACKET; +} + +static void createScancodeTable() +{ + // Scancodes are made from OR'ed (scancode | extended) + // Letters + s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; + s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; + s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; + s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; + s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; + s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; + s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; + s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; + s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; + s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; + s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; + s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; + s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; + s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; + s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; + s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; + s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; + s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; + s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; + s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; + s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; + s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; + s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; + s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; + s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; + s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; + + // Numbers (top row) + s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; + s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; + s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; + s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; + s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; + s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; + s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; + s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; + s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; + s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; + + // Function + s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; + s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; + s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; + s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; + s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; + s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; + s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; + s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; + s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; + s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; + s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; + s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; + + // Control + s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; + s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; + s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; + s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; + s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; + s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; + s_Keyboard.scancodes[0x145] = PAL_SCANCODE_NUMLOCK; + s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; + s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; + s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; + s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; + s_Keyboard.scancodes[0x11D] = PAL_SCANCODE_RCTRL; + s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; + s_Keyboard.scancodes[0x138] = PAL_SCANCODE_RALT; + + // Arrows + s_Keyboard.scancodes[0x14B] = PAL_SCANCODE_LEFT; + s_Keyboard.scancodes[0x14D] = PAL_SCANCODE_RIGHT; + s_Keyboard.scancodes[0x148] = PAL_SCANCODE_UP; + s_Keyboard.scancodes[0x150] = PAL_SCANCODE_DOWN; + + // Navigation + s_Keyboard.scancodes[0x152] = PAL_SCANCODE_INSERT; + s_Keyboard.scancodes[0x153] = PAL_SCANCODE_DELETE; + s_Keyboard.scancodes[0x147] = PAL_SCANCODE_HOME; + s_Keyboard.scancodes[0x14F] = PAL_SCANCODE_END; + s_Keyboard.scancodes[0x149] = PAL_SCANCODE_PAGEUP; + s_Keyboard.scancodes[0x151] = PAL_SCANCODE_PAGEDOWN; + + // Keypad + s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; + s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; + s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; + s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; + s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; + s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; + s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; + s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; + s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; + s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; + s_Keyboard.scancodes[0x11C] = PAL_SCANCODE_KP_ENTER; + s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; + s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; + s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; + s_Keyboard.scancodes[0x135] = PAL_SCANCODE_KP_DIVIDE; + s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; + s_Keyboard.scancodes[0x059] = PAL_SCANCODE_KP_EQUAL; + + // Misc + s_Keyboard.scancodes[0x137] = PAL_SCANCODE_PRINTSCREEN; + s_Keyboard.scancodes[0x146] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[0x045] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[0x15D] = PAL_SCANCODE_MENU; + s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; + s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; + s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; + s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; + s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; + s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; + s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; + s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; + s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; + s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; + s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; + s_Keyboard.scancodes[0x15B] = PAL_SCANCODE_LSUPER; + s_Keyboard.scancodes[0x15C] = PAL_SCANCODE_RSUPER; +} + +PalResult win32InitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance) +{ + s_Win32.maxWindowData = 32; + s_Win32.windowData = + palAllocate(s_Win32.allocator, sizeof(WindowData) * s_Win32.maxWindowData, 0); + + // user provided instance + if (preferredInstance) { + s_Win32.instance = preferredInstance; + } else { + s_Win32.instance = GetModuleHandleW(nullptr); + } + + // load default cursor + s_Win32.defaultCursor = LoadCursorW(NULL, IDC_ARROW); + + // register class + WNDCLASSEXW wc = {0}; + wc.cbSize = sizeof(WNDCLASSEXW); + wc.hCursor = s_Win32.defaultCursor; + wc.hIcon = LoadIconW(NULL, IDI_APPLICATION); + wc.hIconSm = LoadIconW(NULL, IDI_APPLICATION); + wc.hInstance = s_Win32.instance; + wc.lpfnWndProc = videoProc; + wc.lpszClassName = PAL_VIDEO_CLASS; + wc.style = CS_OWNDC; + + if (!RegisterClassExW(&wc)) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // create hidden window + s_Win32.hiddenWindow = CreateWindowExW( + 0, + PAL_VIDEO_CLASS, + L"HiddenWindow", + WS_OVERLAPPEDWINDOW, + 0, + 0, + 8, + 8, + nullptr, + nullptr, + s_Win32.instance, + nullptr); + + if (!s_Win32.hiddenWindow) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // set a flag to check if the window has been created + SetPropW(s_Win32.hiddenWindow, PAL_VIDEO_PROP, &s_Event); + + // register raw input for mice to get delta + RAWINPUTDEVICE rid = {0}; + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = s_Win32.hiddenWindow; + rid.usUsage = 0x02; + rid.usUsagePage = 0x01; + if (!RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE))) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // create mapping table + createKeycodeTable(); + createScancodeTable(); + + // load shared libraries + // shcore + s_Win32.shcore = LoadLibraryA("shcore.dll"); + if (s_Win32.shcore) { + s_Win32.getDpiForMonitor = + (GetDpiForMonitorFn)GetProcAddress(s_Win32.shcore, "GetDpiForMonitor"); + + s_Win32.setProcessAwareness = + (SetProcessAwarenessFn)GetProcAddress(s_Win32.shcore, "SetProcessDpiAwareness"); + } + + // clang-format off + // gdi functios + s_Win32.gdi = LoadLibraryA("gdi32.dll"); + if (s_Win32.gdi) { + s_Win32.createDIBSection = (CreateDIBSectionFn)GetProcAddress( + s_Win32.gdi, + "CreateDIBSection"); + + s_Win32.createBitmap = (CreateBitmapFn)GetProcAddress( + s_Win32.gdi, + "CreateBitmap"); + + s_Win32.deleteObject = (DeleteObjectFn)GetProcAddress( + s_Win32.gdi, + "DeleteObject"); + + s_Win32.describePixelFormat = (DescribePixelFormatFn)GetProcAddress( + s_Win32.gdi, + "DescribePixelFormat"); + + s_Win32.setPixelFormat = (SetPixelFormatFn)GetProcAddress( + s_Win32.gdi, + "SetPixelFormat"); + } + // clang-format on + + // set features + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION; + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; + s_Win32.features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; + s_Win32.features |= PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW; + s_Win32.features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_SET_MODE; + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; + s_Win32.features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_SIZE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_POS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_POS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_TITLE; + s_Win32.features |= PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX; + s_Win32.features |= PAL_VIDEO_FEATURE_NO_MINIMIZEBOX; + s_Win32.features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_STYLE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_STYLE; + s_Win32.features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; + s_Win32.features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_ICON; + + s_Win32.features |= PAL_VIDEO_FEATURE_TOPMOST_WINDOW; + s_Win32.features |= PAL_VIDEO_FEATURE_DECORATED_WINDOW; + s_Win32.features |= PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR; + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY; + s_Win32.features |= PAL_VIDEO_FEATURE_FOREIGN_WINDOWS; + s_Win32.features |= PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE; + s_Win32.features |= PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR; + + if (s_Win32.getDpiForMonitor && s_Win32.setProcessAwareness) { + s_Win32.features |= PAL_VIDEO_FEATURE_HIGH_DPI; + s_Win32.setProcessAwareness(PROCESS_DPI_AWARE); + } + + s_Win32.allocator = allocator; + s_Win32.eventDriver = eventDriver; + s_Mouse.pushMouseDelta = PAL_TRUE; + return PAL_RESULT_SUCCESS; +} + +void win32ShutdownVideo() +{ + if (s_Win32.shcore) { + FreeLibrary(s_Win32.shcore); + } + + FreeLibrary(s_Win32.gdi); + DestroyWindow(s_Win32.hiddenWindow); + UnregisterClassW(PAL_VIDEO_CLASS, s_Win32.instance); + palFree(s_Win32.allocator, s_Win32.windowData); + + memset(&s_Win32, 0, sizeof(VideoWin32)); + memset(&s_Keyboard, 0, sizeof(Keyboard)); + memset(&s_Mouse, 0, sizeof(Mouse)); + + s_Win32.windowData = nullptr; +} + +void win32UpdateVideo() +{ + s_Mouse.dx = 0; + s_Mouse.dy = 0; + + MSG msg; + while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + // push pending move and reszie events + if (s_Event.pendingResize) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_SIZE; + event.data = palPackUint32(s_Event.width, s_Event.height); + event.data2 = palPackPointer(s_Event.window); + palPushEvent(s_Win32.eventDriver, &event); + + if (s_Event.pendingState) { + PalEvent event = {0}; + event.data = s_Event.state; + event.data2 = palPackPointer(s_Event.window); + event.type = PAL_EVENT_TYPE_WINDOW_STATE; + palPushEvent(s_Win32.eventDriver, &event); + s_Event.pendingState = PAL_FALSE; + } + + s_Event.pendingResize = PAL_FALSE; + + } else if (s_Event.pendingMove) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_WINDOW_MOVE; + event.data = palPackInt32(s_Event.x, s_Event.y); + event.data2 = palPackPointer(s_Event.window); + palPushEvent(s_Win32.eventDriver, &event); + s_Event.pendingMove = PAL_FALSE; + } +} + +PalVideoFeatures win32GetVideoFeatures() +{ + return s_Win32.features; +} + +const PalBool* win32GetKeycodeState() +{ + return s_Keyboard.keycodeState; +} + +const PalBool* win32GetScancodeState() +{ + return s_Keyboard.scancodeState; +} + +const PalBool* win32GetMouseState() +{ + return s_Mouse.state; +} + +void win32GetMouseDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.dx; + } + + if (dy) { + *dy = (float)s_Mouse.dy; + } +} + +void win32GetMouseWheelDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.WheelX; + } + + if (dy) { + *dy = (float)s_Mouse.WheelY; + } +} + +void* win32GetInstance() +{ + return (void*)s_Win32.instance; +} + +#endif // _WIN32 diff --git a/src/video/win32/pal_video_win32.h b/src/video/win32/pal_video_win32.h new file mode 100644 index 00000000..de085d8c --- /dev/null +++ b/src/video/win32/pal_video_win32.h @@ -0,0 +1,107 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_VIDEO_WIN32_H +#define _PAL_VIDEO_WIN32_H +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +// set unicode +#ifndef UNICODE +#define UNICODE +#endif // UNICODE + +#include "pal2/pal_video.h" +#include + +#define PAL_VIDEO_CLASS L"PALVideoClass" +#define PAL_VIDEO_PROP L"PalVideoData" +#define WINDOW_NAME_SIZE 256 + +LRESULT CALLBACK videoProc( + HWND hwnd, + UINT msg, + WPARAM wParam, + LPARAM lParam); + +typedef HRESULT(WINAPI* GetDpiForMonitorFn)( + HMONITOR, + int32_t, + UINT*, + UINT*); + +typedef HRESULT(WINAPI* SetProcessAwarenessFn)(int32_t); + +typedef HBITMAP(WINAPI* CreateDIBSectionFn)( + HDC, + const BITMAPINFO*, + UINT, + VOID**, + HANDLE, + DWORD); + +typedef HBITMAP(WINAPI* CreateBitmapFn)( + int, + int, + UINT, + UINT, + CONST VOID*); + +typedef BOOL(WINAPI* DeleteObjectFn)(HGDIOBJ); + +typedef int(WINAPI* DescribePixelFormatFn)( + HDC, + int, + UINT, + LPPIXELFORMATDESCRIPTOR); + +typedef BOOL(WINAPI* SetPixelFormatFn)( + HDC, + int, + CONST PIXELFORMATDESCRIPTOR*); + +typedef struct { + PalBool used; + PalBool isAttached; + PalWindowState state; + HCURSOR cursor; + LONG_PTR wndProc; +} WindowData; + +typedef struct { + int32_t maxWindowData; + PalVideoFeatures features; + const PalAllocator* allocator; + PalEventDriver* eventDriver; + HINSTANCE shcore; + GetDpiForMonitorFn getDpiForMonitor; + SetProcessAwarenessFn setProcessAwareness; + + HINSTANCE gdi; + CreateDIBSectionFn createDIBSection; + CreateBitmapFn createBitmap; + DeleteObjectFn deleteObject; + DescribePixelFormatFn describePixelFormat; + SetPixelFormatFn setPixelFormat; + + HINSTANCE instance; + HWND hiddenWindow; + HCURSOR defaultCursor; + WindowData* windowData; +} VideoWin32; + +extern VideoWin32 s_Win32; + +#endif // _WIN32 +#endif // _PAL_VIDEO_WIN32_H \ No newline at end of file diff --git a/src/video/win32/pal_window_win32.c b/src/video/win32/pal_window_win32.c new file mode 100644 index 00000000..1291a896 --- /dev/null +++ b/src/video/win32/pal_window_win32.c @@ -0,0 +1,601 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifdef _WIN32 +#include "pal_video_win32.h" + +static WindowData* getFreeWindowData() +{ + for (int i = 0; i < s_Win32.maxWindowData; ++i) { + if (!s_Win32.windowData[i].used) { + s_Win32.windowData[i].used = PAL_TRUE; + return &s_Win32.windowData[i]; + } + } + + // resize the data array + // It is rare for a user to create and manage + // 32 windows at the same time + WindowData* data = nullptr; + int count = s_Win32.maxWindowData * 2; // double the size + int freeIndex = s_Win32.maxWindowData + 1; + data = palAllocate(s_Win32.allocator, sizeof(WindowData) * count, 0); + if (data) { + memcpy(data, s_Win32.windowData, s_Win32.maxWindowData * sizeof(WindowData)); + + palFree(s_Win32.allocator, s_Win32.windowData); + s_Win32.windowData = data; + s_Win32.maxWindowData = count; + + s_Win32.windowData[freeIndex].used = PAL_TRUE; + return &s_Win32.windowData[freeIndex]; + } + return nullptr; +} + +PalResult win32CreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + HWND handle = nullptr; + PalMonitor* monitor = nullptr; + PalMonitorInfo monitorInfo; + + uint32_t style = WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED; + uint32_t exStyle = 0; + + // no minimize box + if (!(info->style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX)) { + style |= WS_MINIMIZEBOX; + } + + // no maximize box + if (!(info->style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX)) { + style |= WS_MAXIMIZEBOX; + } + + // resizable window + if (info->style & PAL_WINDOW_STYLE_RESIZABLE) { + style |= WS_THICKFRAME; + + } else { + // not resizable. We remove the maximizebox even if user requested + style &= ~WS_MAXIMIZEBOX; + } + + // transparent window + if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { + exStyle |= WS_EX_LAYERED; + } + + // tool window + if (info->style & PAL_WINDOW_STYLE_TOOL) { + exStyle |= WS_EX_TOOLWINDOW; + } + + // topmost window + if (info->style & PAL_WINDOW_STYLE_TOPMOST) { + exStyle |= WS_EX_TOPMOST; + } + + // get monitor + if (info->monitor) { + monitor = info->monitor; + + } else { + // get primary monitor + monitor = (PalMonitor*)MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY); + if (!monitor) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + } + + // compose position. + palGetMonitorInfo(monitor, &monitorInfo); + int32_t x, y = 0; + // the position and size must be scaled with the dpi before this call + if (info->center) { + x = monitorInfo.x + (monitorInfo.width - info->width) / 2; + y = monitorInfo.y + (monitorInfo.height - info->height) / 2; + + } else { + // we set 100 for each axix + x = monitorInfo.x + 100; + y = monitorInfo.y + 100; + } + + // adjust the window size + RECT rect = {0, 0, 0, 0}; + rect.right = info->width; + rect.bottom = info->height; + AdjustWindowRectEx(&rect, style, 0, exStyle); + + wchar_t buffer[WINDOW_NAME_SIZE]; + MultiByteToWideChar(CP_UTF8, 0, info->title, -1, buffer, 256); + + // create the window + handle = CreateWindowExW( + exStyle, + PAL_VIDEO_CLASS, + buffer, + style, + x, + y, + rect.right - rect.left, + rect.bottom - rect.top, + nullptr, + nullptr, + s_Win32.instance, + nullptr); + + if (!handle) { + return palMakeResult( + PAL_RESULT_CODE_PLATFORM_FAILURE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + // set the pixel format is set + if (info->fbConfigIndex) { + // clang-format off + if (info->fbConfigBackend == PAL_FBCONFIG_BACKEND_EGL || + info->fbConfigBackend == PAL_FBCONFIG_BACKEND_GLX) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + // clang-format on + + HDC hdc = GetDC(handle); + // since we have the pixel format already + // we ask the OS (platform) to fill the pfd struct for us from that + // index + PIXELFORMATDESCRIPTOR pfd; + if (!s_Win32.describePixelFormat( + hdc, + info->fbConfigIndex, + sizeof(PIXELFORMATDESCRIPTOR), + &pfd)) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_ARGUMENT, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + s_Win32.setPixelFormat(hdc, info->fbConfigIndex, &pfd); + ReleaseDC(handle, hdc); + } + + // show, maximize and minimize + int32_t showFlag = SW_HIDE; + // maximize + if (info->state == PAL_WINDOW_STATE_MAXIMIZED) { + showFlag = SW_MAXIMIZE; + data->state = PAL_WINDOW_STATE_MAXIMIZED; + } + + // minimized + if (info->state == PAL_WINDOW_STATE_MINIMIZED) { + showFlag = SW_MINIMIZE; + data->state = PAL_WINDOW_STATE_MINIMIZED; + } + + // shown + if (info->show) { + if (showFlag == SW_HIDE) { + // change only if maximize and minimize are not set + showFlag = SW_SHOW; + data->state = PAL_WINDOW_STATE_RESTORED; + } + } + + ShowWindow(handle, showFlag); + UpdateWindow(handle); + + if (info->style & PAL_WINDOW_STYLE_BORDERLESS) { + // revert changes + SetWindowLongPtrW(handle, GWL_STYLE, WS_POPUP); + SetWindowLongPtrW(handle, GWL_EXSTYLE, WS_EX_APPWINDOW); + + // force a frame update + SetWindowPos( + handle, + nullptr, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + data->isAttached = PAL_FALSE; + data->cursor = nullptr; + data->wndProc = (LONG_PTR)videoProc; + SetPropW(handle, PAL_VIDEO_PROP, data); + *outWindow = (PalWindow*)handle; + return PAL_RESULT_SUCCESS; +} + +void win32DestroyWindow(PalWindow* window) +{ + WindowData* data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + // destroy only PAL created window + if (data->isAttached) { + return; + } + + DestroyWindow((HWND)window); + data->used = PAL_FALSE; +} + +void win32MinimizeWindow(PalWindow* window) +{ + ShowWindow((HWND)window, SW_MINIMIZE); +} + +void win32MaximizeWindow(PalWindow* window) +{ + ShowWindow((HWND)window, SW_MAXIMIZE); +} + +void win32RestoreWindow(PalWindow* window) +{ + ShowWindow((HWND)window, SW_RESTORE); +} + +void win32ShowWindow(PalWindow* window) +{ + ShowWindow((HWND)window, SW_SHOW); +} + +void win32HideWindow(PalWindow* window) +{ + ShowWindow((HWND)window, SW_HIDE); +} + +void win32FlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + DWORD flags = 0; + if (info->flags == PAL_FLASH_FLAG_STOP) { + flags = FLASHW_STOP; + + } else { + if (info->flags & PAL_FLASH_FLAG_CAPTION) { + flags |= FLASHW_CAPTION; + } + if (info->flags & PAL_FLASH_FLAG_TRAY) { + flags |= FLASHW_TRAY; + flags |= FLASHW_TIMERNOFG; + } + } + + FLASHWINFO flashInfo = {0}; + flashInfo.cbSize = sizeof(FLASHWINFO); + flashInfo.dwFlags = flags; + flashInfo.dwTimeout = info->interval; + flashInfo.hwnd = (HWND)window; + flashInfo.uCount = info->count; + FlashWindowEx(&flashInfo); +} + +void win32GetWindowStyle( + PalWindow* window, + PalWindowStyle* outStyle) +{ + PalWindowStyle windowStyle = 0; + DWORD style = (DWORD)GetWindowLongPtrW((HWND)window, GWL_STYLE); + DWORD exStyle = (DWORD)GetWindowLongPtrW((HWND)window, GWL_EXSTYLE); + + // check if we can resize + if (style & WS_THICKFRAME) { + windowStyle |= PAL_WINDOW_STYLE_RESIZABLE; + } + + // check if we are transparent + if (exStyle & WS_EX_LAYERED) { + windowStyle |= PAL_WINDOW_STYLE_TRANSPARENT; + } + + // check if we are a topmost window + if (exStyle & WS_EX_TOPMOST) { + windowStyle |= PAL_WINDOW_STYLE_TOPMOST; + } + + // check if we have a minimize box + if (!(style & WS_MINIMIZEBOX)) { + windowStyle |= PAL_WINDOW_STYLE_NO_MINIMIZEBOX; + } + + // check if we have a maximize box + if (!(style & WS_MAXIMIZEBOX)) { + windowStyle |= PAL_WINDOW_STYLE_NO_MAXIMIZEBOX; + } + + // check if its a tool window + if (exStyle & WS_EX_TOOLWINDOW) { + windowStyle |= PAL_WINDOW_STYLE_TOOL; + } + + // we check borderless last since it will overwrite other styles + if (style & WS_POPUP) { + windowStyle |= PAL_WINDOW_STYLE_BORDERLESS; + + // we remove minimize and maximize box if set + windowStyle &= ~PAL_WINDOW_STYLE_NO_MINIMIZEBOX; + windowStyle &= ~PAL_WINDOW_STYLE_NO_MAXIMIZEBOX; + } + + *outStyle = windowStyle; +} + +void win32GetWindowMonitor( + PalWindow* window, + PalMonitor** outMonitor) +{ + HMONITOR monitor = MonitorFromWindow((HWND)window, MONITOR_DEFAULTTONEAREST); + *outMonitor = (PalMonitor*)monitor; +} + +void win32GetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer) +{ + wchar_t buffer[WINDOW_NAME_SIZE]; + GetWindowTextW((HWND)window, buffer, WINDOW_NAME_SIZE); + int len = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, nullptr, 0, 0, 0); + if (outSize) { + *outSize = len - 1; + } + + // see if user provided a buffer and write to it + if (outBuffer && bufferSize > 0) { + int write = (int)bufferSize - 1; + WideCharToMultiByte(CP_UTF8, 0, buffer, -1, outBuffer, write + 1, 0, 0); + outBuffer[write < len - 1 ? write : len - 1] = '\0'; + } +} + +void win32GetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + RECT rect; + GetWindowRect((HWND)window, &rect); + if (x) { + *x = rect.left; + } + + if (y) { + *y = rect.top; + } +} + +void win32GetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height) +{ + RECT rect; + GetWindowRect((HWND)window, &rect); + if (width) { + *width = rect.right - rect.left; + } + + if (height) { + *height = rect.bottom - rect.top; + } +} + +void win32GetWindowState( + PalWindow* window, + PalWindowState* outState) +{ + WINDOWPLACEMENT wp = {0}; + GetWindowPlacement((HWND)window, &wp); + if (wp.showCmd == SW_MINIMIZE) { + *outState = PAL_WINDOW_STATE_MINIMIZED; + + } else if (wp.showCmd == SW_MAXIMIZE) { + *outState = PAL_WINDOW_STATE_MAXIMIZED; + + } else if (wp.showCmd == SW_RESTORE || wp.showCmd == SW_NORMAL) { + *outState = PAL_WINDOW_STATE_RESTORED; + } +} + +PalBool win32IsWindowVisible(PalWindow* window) +{ + return IsWindowVisible((HWND)window); +} + +PalWindow* win32GetFocusWindow() +{ + return (PalWindow*)GetFocus(); +} + +void win32GetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info) +{ + info->nativeInstance = (void*)s_Win32.instance; + info->nativeWindow = (void*)window; + info->nativeHandle1 = nullptr; + info->nativeHandle2 = nullptr; + info->nativeHandle3 = nullptr; +} + +void win32SetWindowOpacity( + PalWindow* window, + float opacity) +{ + SetLayeredWindowAttributes((HWND)window, 0, (BYTE)(opacity * 255), LWA_ALPHA); +} + +void win32SetWindowStyle( + PalWindow* window, + PalWindowStyle style) +{ + // convert our style to win32 styles and exStyles all windows have this styles + DWORD win32Style = WS_CAPTION | WS_SYSMENU | WS_OVERLAPPED; + DWORD exStyle = 0; + + // check for resizing + if (style & PAL_WINDOW_STYLE_RESIZABLE) { + win32Style |= WS_THICKFRAME; + } + + // check for transparent window + if (style & PAL_WINDOW_STYLE_TRANSPARENT) { + exStyle |= WS_EX_LAYERED; + } + + // check for topmost window + if (style & PAL_WINDOW_STYLE_TOPMOST) { + exStyle |= WS_EX_TOPMOST; + } + + // check for no minimize box + if (style & PAL_WINDOW_STYLE_NO_MINIMIZEBOX) { + win32Style &= ~WS_MINIMIZEBOX; + } + + // check for maximize box + if (style & PAL_WINDOW_STYLE_NO_MAXIMIZEBOX) { + win32Style &= ~WS_MAXIMIZEBOX; + } + + // check for tool window + if (style & PAL_WINDOW_STYLE_TOOL) { + exStyle |= WS_EX_TOOLWINDOW; + } + + // check for borderless window + if (style & PAL_WINDOW_STYLE_BORDERLESS) { + // revert the styles + win32Style = WS_POPUP; + exStyle = WS_EX_APPWINDOW; + } + + HWND hwnd = (HWND)window; + SetWindowLongPtrW(hwnd, GWL_STYLE, win32Style); + SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle); + + // force a frame update + SetWindowPos( + hwnd, + nullptr, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); +} + +void win32SetWindowTitle( + PalWindow* window, + const char* title) +{ + wchar_t buffer[WINDOW_NAME_SIZE]; + MultiByteToWideChar(CP_UTF8, 0, title, -1, buffer, 256); + SetWindowTextW((HWND)window, buffer); +} + +void win32SetWindowPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + SetWindowPos((HWND)window, nullptr, x, y, 0, 0, SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE); +} + +void win32SetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height) +{ + SetWindowPos( + (HWND)window, + HWND_TOP, + 0, + 0, + width, + height, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER); +} + +void win32SetFocusWindow(PalWindow* window) +{ + SetActiveWindow((HWND)window); +} + +PalResult win32AttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + WindowData* data = getFreeWindowData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + PalWindow* window = (PalWindow*)windowHandle; + data->isAttached = PAL_TRUE; + data->wndProc = SetWindowLongPtrW((HWND)windowHandle, GWLP_WNDPROC, (LONG_PTR)videoProc); + + // use default PAL video cursor + // there is no way to get the cursor set on the native window + data->cursor = nullptr; + + // get state + palGetWindowState(window, &data->state); + SetPropW((HWND)window, PAL_VIDEO_PROP, data); + + *outWindow = window; + return PAL_RESULT_SUCCESS; +} + +PalResult win32DetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + WindowData* data = nullptr; + data = (WindowData*)GetPropW((HWND)window, PAL_VIDEO_PROP); + if (!data) { + return palMakeResult( + PAL_RESULT_CODE_INVALID_HANDLE, + PAL_RESULT_SOURCE_WIN32, + GetLastError()); + } + + if (data->isAttached == PAL_FALSE) { + // window is owned by PAL + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + data->used = PAL_FALSE; + SetWindowLongPtrW((HWND)window, GWLP_WNDPROC, data->wndProc); + if (outWindowHandle) { + *outWindowHandle = (void*)window; + } + + return PAL_RESULT_SUCCESS; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/video/x11/pal_cursor_x11.c b/src/video/x11/pal_cursor_x11.c new file mode 100644 index 00000000..ed580f87 --- /dev/null +++ b/src/video/x11/pal_cursor_x11.c @@ -0,0 +1,161 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_X11_BACKEND == 1 +#include "pal_x11.h" + +PalResult xCreateCursor( + const PalCursorCreateInfo* info, + PalCursor** outCursor) +{ + XcursorImage* image = s_X11.cursorImageCreate(info->width, info->height); + image->xhot = info->xHotspot; + image->yhot = info->yHotspot; + + // convert from RGBA8 to ARGB32 + for (int i = 0; i < info->width * info->height; i++) { + uint8_t r = info->pixels[i * 4 + 0]; // Red + uint8_t g = info->pixels[i * 4 + 1]; // Green + uint8_t b = info->pixels[i * 4 + 2]; // Blue + uint8_t a = info->pixels[i * 4 + 3]; // Alpha + + // clang-format off + image->pixels[i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } + + Cursor cursor = s_X11.cursorImageLoadCursor(s_X11.display, image); + if (!cursor) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + s_X11.cursorImageDestroy(image); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} + +PalResult xCreateCursorFrom( + PalCursorType type, + PalCursor** outCursor) +{ + int shape; + Cursor cursor; + switch (type) { + case PAL_CURSOR_TYPE_ARROW: { + shape = XC_left_ptr; + break; + } + + case PAL_CURSOR_TYPE_HAND: { + shape = XC_hand2; + break; + } + + case PAL_CURSOR_TYPE_CROSS: { + shape = XC_cross; + break; + } + + case PAL_CURSOR_TYPE_IBEAM: { + shape = XC_xterm; + break; + } + + case PAL_CURSOR_TYPE_WAIT: { + shape = XC_watch; + break; + } + } + + cursor = s_X11.createFontCursor(s_X11.display, shape); + *outCursor = TO_PAL_HANDLE(PalCursor, cursor); + return PAL_RESULT_SUCCESS; +} + +void xDestroyCursor(PalCursor* cursor) +{ + s_X11.freeCursor(s_X11.display, FROM_PAL_HANDLE(Cursor, cursor)); +} + +void xShowCursor(PalBool show) +{ + // x11 does not support Hiding and showing cursor + return; +} + +void xClipCursor( + PalWindow* window, + PalBool clip) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + if (clip) { + s_X11.grabPointer( + s_X11.display, + xWin, + True, + 0, + GrabModeAsync, + GrabModeAsync, + xWin, + None, + CurrentTime); + + } else { + s_X11.ungrabPointer(s_X11.display, CurrentTime); + } +} + +void xGetCursorPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + Window root, child; + int rootX, rootY, winX, winY; + unsigned int mask; + s_X11.queryPointer(s_X11.display, xWin, &root, &child, &rootX, &rootY, &winX, &winY, &mask); + if (x) { + *x = winX; + } + + if (y) { + *y = winY; + } +} + +void xSetCursorPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + s_X11.warpPointer(s_X11.display, None, xWin, 0, 0, 0, 0, x, y); + s_X11.flush(s_X11.display); +} + +void xSetWindowCursor( + PalWindow* window, + PalCursor* cursor) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + Window xCursor = FROM_PAL_HANDLE(Cursor, cursor); + if (xCursor) { + s_X11.defineCursor(s_X11.display, xWin, xCursor); + + } else { + s_X11.undefineCursor(s_X11.display, xWin); + } + + s_X11.flush(s_X11.display); +} + +#endif // PAL_HAS_X11_BACKEND \ No newline at end of file diff --git a/src/video/x11/pal_icon_x11.c b/src/video/x11/pal_icon_x11.c new file mode 100644 index 00000000..b03069cb --- /dev/null +++ b/src/video/x11/pal_icon_x11.c @@ -0,0 +1,76 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_X11_BACKEND == 1 +#include "pal_x11.h" + +PalResult xCreateIcon( + const PalIconCreateInfo* info, + PalIcon** outIcon) +{ + uint64_t totalPixels = 2 + (uint64_t)(info->width * info->height); + uint64_t totalBytes = sizeof(unsigned long) * totalPixels; + unsigned long* icon = palAllocate(s_X11.allocator, totalBytes, 0); + if (!icon) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // store width and height and populate data with icon pixels + // [width][height][pixels] + icon[0] = (unsigned long)info->width; + icon[1] = (unsigned long)info->height; + + // convert from RGBA8 to ARGB32 + for (int i = 0; i < info->width * info->height; i++) { + uint8_t r = info->pixels[i * 4 + 0]; // Red + uint8_t g = info->pixels[i * 4 + 1]; // Green + uint8_t b = info->pixels[i * 4 + 2]; // Blue + uint8_t a = info->pixels[i * 4 + 3]; // Alpha + + // clang-format off + icon[2 + i] = ((unsigned long)a << 24) | + ((unsigned long)r << 16) | + ((unsigned long)g << 8) | + ((unsigned long)b); + // clang-format on + } + + *outIcon = TO_PAL_HANDLE(PalIcon, icon); + return PAL_RESULT_SUCCESS; +} + +void xDestroyIcon(PalIcon* icon) +{ + if (icon) { + palFree(s_X11.allocator, icon); + } +} + +void xSetWindowIcon( + PalWindow* window, + PalIcon* icon) +{ + WindowData* winData = nullptr; + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&winData); + + unsigned long* iconData = FROM_PAL_HANDLE(unsigned long*, icon); + uint64_t totalPixels = 2 + iconData[0] * iconData[1]; + s_X11.changeProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_ICON, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)iconData, + (int)totalPixels); + + s_X11.flush(s_X11.display); +} + +#endif // PAL_HAS_X11_BACKEND \ No newline at end of file diff --git a/src/video/x11/pal_monitor_x11.c b/src/video/x11/pal_monitor_x11.c new file mode 100644 index 00000000..277b5502 --- /dev/null +++ b/src/video/x11/pal_monitor_x11.c @@ -0,0 +1,374 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_X11_BACKEND == 1 +#include "pal_x11.h" +#include + +PalResult xEnumerateMonitors( + uint32_t* count, + PalMonitor** outMonitors) +{ + int _count = 0; + int maxCount = outMonitors ? *count : 0; + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + if (outputInfo->connection == RR_Connected && outputInfo->crtc != None) { + // a monitor + if (outMonitors) { + if (_count < maxCount) { + outMonitors[_count] = TO_PAL_HANDLE(PalMonitor, output); + } + } + _count++; + } + } + + if (!outMonitors) { + *count = _count; + } + return PAL_RESULT_SUCCESS; +} + +void xGetPrimaryMonitor(PalMonitor** outMonitor) +{ + RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); + if (primary) { + *outMonitor = TO_PAL_HANDLE(PalMonitor, primary); + } +} + +void xGetMonitorInfo( + PalMonitor* monitor, + PalMonitorInfo* info) +{ + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return; + } + + // check if its primary monitor + strcpy(info->name, outputInfo->name); + RROutput primary = s_X11.getOutputPrimary(s_X11.display, s_X11.root); + if (monitor == TO_PAL_HANDLE(PalMonitor, primary)) { + info->primary = PAL_TRUE; + } else { + info->primary = PAL_FALSE; + } + + // get monitor pos and size + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + info->x = crtc->x; + info->y = crtc->y; + info->width = crtc->width; + info->height = crtc->height; + + // get refresh rate + double rate = 0; + for (int i = 0; i < resources->nmode; ++i) { + // check for our monitor + if (resources->modes[i].id == crtc->mode) { + XRRModeInfo* mode = &resources->modes[i]; + double tmp = (double)mode->hTotal * (double)mode->vTotal; + rate = (double)mode->dotClock / tmp; + info->refreshRate = rate + 0.5; + break; + } + } + + // orientation + switch (crtc->rotation) { + case RR_Rotate_0: { + info->orientation = PAL_ORIENTATION_LANDSCAPE; + break; + } + + case RR_Rotate_90: { + info->orientation = PAL_ORIENTATION_PORTRAIT; + break; + } + + case RR_Rotate_180: { + info->orientation = PAL_ORIENTATION_LANDSCAPE_FLIPPED; + break; + } + + case RR_Rotate_270: { + info->orientation = PAL_ORIENTATION_PORTRAIT_FLIPPED; + break; + } + + default: { + info->orientation = PAL_ORIENTATION_LANDSCAPE; + } + } + + // get dpi + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; + } + } + + info->dpi = (uint32_t)(closest * 96.0f); + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); +} + +void xEnumerateMonitorModes( + PalMonitor* monitor, + uint32_t* count, + PalMonitorMode* modes) +{ + uint32_t modeCount = 0; + int maxModeCount = modes ? *count : 0; + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + // get the monitor info + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return; + } + + // get supported display modes + for (int i = 0; i < outputInfo->nmode; ++i) { + for (int j = 0; j < resources->nmode; ++j) { + // get the display mode and check if its for our monitor + XRRModeInfo* info = &resources->modes[j]; + if (info->id == outputInfo->modes[i]) { + // check if user supplied a PalMonitorMode array + if (modes) { + if (modeCount < maxModeCount) { + PalMonitorMode* mode = &modes[modeCount]; + mode->width = info->width; + mode->height = info->height; + mode->bpp = s_X11.bpp; + + double tmp = (double)info->hTotal * (double)info->vTotal; + double rate = (double)info->dotClock / tmp; + mode->refreshRate = rate + 0.5; + } + } + modeCount++; + } + } + } + + if (!modes) { + *count = modeCount; + } + + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); +} + +void xGetCurrentMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + if (!outputInfo) { + s_X11.freeScreenResources(resources); + return; + } + + if (outputInfo->connection != RR_Connected) { + s_X11.freeScreenResources(resources); + return; + } + + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + XRRModeInfo* info = nullptr; + for (int i = 0; i < resources->nmode; ++i) { + if (resources->modes[i].id == crtc->mode) { + info = &resources->modes[i]; + break; + } + } + + if (mode) { + mode->width = info->width; + mode->height = info->height; + mode->bpp = s_X11.bpp; + + double tmp = (double)info->hTotal * (double)info->vTotal; + double rate = (double)info->dotClock / tmp; + mode->refreshRate = rate + 0.5; + } + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); +} + +PalResult xSetMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + // get the monitor info + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // find the monitor display mode + RRMode displayMode = findMode(resources, mode); + if (displayMode == None) { + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // apply the display mode + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + int ret = s_X11.setCrtcConfig( + s_X11.display, + resources, + outputInfo->crtc, + CurrentTime, + crtc->x, + crtc->y, + displayMode, + crtc->rotation, + &output, + 1); + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + if (ret != Success) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + return PAL_RESULT_SUCCESS; +} + +PalResult xValidateMonitorMode( + PalMonitor* monitor, + PalMonitorMode* mode) +{ + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; +} + +PalResult xSetMonitorOrientation( + PalMonitor* monitor, + PalOrientation orientation) +{ + XRRScreenResources* resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + // get the monitor info + RROutput output = FROM_PAL_HANDLE(RROutput, monitor); + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + if (!outputInfo) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + if (outputInfo->connection != RR_Connected) { + // invalid monitor + s_X11.freeScreenResources(resources); + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // get the current display mode + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + + // check if the new orientation is supported + Rotation rotation = 0; + switch (orientation) { + case PAL_ORIENTATION_LANDSCAPE: { + rotation = RR_Rotate_0; + break; + } + + case PAL_ORIENTATION_PORTRAIT: { + rotation = RR_Rotate_90; + break; + } + + case PAL_ORIENTATION_LANDSCAPE_FLIPPED: { + rotation = RR_Rotate_180; + break; + } + + case PAL_ORIENTATION_PORTRAIT_FLIPPED: { + rotation = RR_Rotate_270; + break; + } + } + + if (!(crtc->rotations & rotation)) { + return PAL_RESULT_CODE_INVALID_OPERATION; + } + + int ret = s_X11.setCrtcConfig( + s_X11.display, + resources, + outputInfo->crtc, + CurrentTime, + crtc->x, + crtc->y, + crtc->mode, + rotation, + &output, + 1); + + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + s_X11.freeScreenResources(resources); + if (ret != Success) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_X11_BACKEND \ No newline at end of file diff --git a/src/video/x11/pal_video_x11.c b/src/video/x11/pal_video_x11.c new file mode 100644 index 00000000..4223386f --- /dev/null +++ b/src/video/x11/pal_video_x11.c @@ -0,0 +1,1525 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_X11_BACKEND == 1 +#include "pal_x11.h" +#include +#include +#include + +#define NULL_BUTTON_SERIAL 0xffffffffU + +typedef struct { + int32_t lastX; + int32_t lastY; + int32_t dx; + int32_t dy; + int32_t WheelX; + int32_t WheelY; + PalBool state[PAL_MOUSE_BUTTON_COUNT]; +} Mouse; + +typedef struct { + PalBool scancodeState[PAL_SCANCODE_COUNT]; + PalBool keycodeState[PAL_KEYCODE_COUNT]; + int scancodes[512]; + int keycodes[256]; +} Keyboard; + +X11 s_X11 = {0}; +X11Atoms s_X11Atoms = {0}; +static Mouse s_Mouse = {0}; +static Keyboard s_Keyboard = {0}; + +static void resetMonitorData() +{ + memset(s_X11.monitorData, 0, s_X11.maxMonitorData * sizeof(MonitorData)); +} + +RRMode findMode( + XRRScreenResources* resources, + const PalMonitorMode* mode) +{ + for (int i = 0; i < resources->nmode; ++i) { + XRRModeInfo* info = &resources->modes[i]; + double tmp = (double)info->hTotal * (double)info->vTotal; + double rate = (double)info->dotClock / tmp; + + // compare with width, height and refresh rate + if (info->width == mode->width && info->height == mode->height && + (uint32_t)(rate + 0.5) == mode->refreshRate) { + return info->id; + } + } + return None; +} + +static void checkFeatures() +{ + // cache this atoms + X_INTERN(WM_DELETE_WINDOW); + X_INTERN(_NET_SUPPORTED); + X_INTERN(_NET_WM_STATE); + X_INTERN(_NET_WM_STATE_ABOVE); + X_INTERN(_NET_WM_STATE_MAXIMIZED_VERT); + X_INTERN(_NET_WM_STATE_MAXIMIZED_HORZ); + X_INTERN(_NET_WM_STATE_HIDDEN); + X_INTERN(_NET_WM_NAME); + X_INTERN(UTF8_STRING); + X_INTERN(_NET_WM_WINDOW_TYPE_UTILITY); + X_INTERN(_NET_WM_DESKTOP); + X_INTERN(_NET_WM_STATE_DEMANDS_ATTENTIONS); + X_INTERN(_NET_WM_WINDOW_OPACITY); + X_INTERN(_NET_WM_WINDOW_TYPE); + X_INTERN(_NET_WM_WINDOW_TYPE_SPLASH); + X_INTERN(_NET_WM_PID); + X_INTERN(_WM_CLASS); + X_INTERN(_NET_ACTIVE_WINDOW); + X_INTERN(_NET_WM_ICON); + + // check for support from the window manager + Atom type; + int format; + unsigned long count, bytesAfters; + Atom* supportedAtoms = nullptr; + s_X11.getWindowProperty( + s_X11.display, + s_X11.root, + s_X11Atoms._NET_SUPPORTED, + 0, + (~0L), + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfters, + (unsigned char**)&supportedAtoms); + + PalVideoFeatures features = 0; + for (unsigned long i = 0; i < count; ++i) { + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + features |= PAL_VIDEO_FEATURE_WINDOW_SET_STATE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_STATE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH) { + features |= PAL_VIDEO_FEATURE_BORDERLESS_WINDOW; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_NAME) { + s_X11Atoms.unicodeTitle = PAL_TRUE; + } + + if (supportedAtoms[i] == s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY) { + features |= PAL_VIDEO_FEATURE_TOOL_WINDOW; + } + } + + // check for transparent windows + Atom compositor = s_X11.internAtom(s_X11.display, "_NET_WM_CM_S0", True); + if (compositor != None) { + Window owner = s_X11.getSelectionOwner(s_X11.display, compositor); + if (owner != None) { + features |= PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW; + } + } + + // general features + features |= PAL_VIDEO_FEATURE_MULTI_MONITORS; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION; + features |= PAL_VIDEO_FEATURE_MONITOR_SET_MODE; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_MODE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_SIZE; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY; + + features |= PAL_VIDEO_FEATURE_CLIP_CURSOR; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS; + features |= PAL_VIDEO_FEATURE_CURSOR_SET_POS; + features |= PAL_VIDEO_FEATURE_CURSOR_GET_POS; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_GET_TITLE; + features |= PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY; + + features |= PAL_VIDEO_FEATURE_WINDOW_SET_ICON; + features |= PAL_VIDEO_FEATURE_TOPMOST_WINDOW; + features |= PAL_VIDEO_FEATURE_DECORATED_WINDOW; + features |= PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY; + features |= PAL_VIDEO_FEATURE_FOREIGN_WINDOWS; + features |= PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR; + + s_X11.features = features; + s_X11.free(supportedAtoms); +} + +static PalWindowState queryWindowState(Window xWin) +{ + Atom type; + int format; + unsigned long count, bytesAfter; + Atom* atoms = nullptr; + PalWindowState state = PAL_WINDOW_STATE_RESTORED; + + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_STATE, + 0, + 1024, + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfter, + (unsigned char**)&atoms); + + for (unsigned int i = 0; i < count; i++) { + if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (atoms[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (atoms[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + state = PAL_WINDOW_STATE_MINIMIZED; + } + } + + s_X11.free(atoms); + return state; +} + +static void cacheMonitors() +{ + resetMonitorData(); + XRRScreenResources* resources = nullptr; + resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + XRROutputInfo* info = s_X11.getOutputInfo(s_X11.display, resources, output); + if (info->connection == RR_Connected && info->crtc != None) { + // get monitor data and update info + PalMonitor* monitor = TO_PAL_HANDLE(PalMonitor, output); + MonitorData* data = nullptr; + data = xGetFreeMonitorData(); + if (!data) { + return; + } + + data->monitor = monitor; + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, info->crtc); + + // get DPI + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; + } + } + + data->dpi = (uint32_t)(closest * 96.0f); + data->w = crtc->width; + data->h = crtc->height; + data->x = crtc->x; + data->y = crtc->y; + + s_X11.freeCrtcInfo(crtc); + s_X11.monitorCount++; + } + + s_X11.freeOutputInfo(info); + } + + s_X11.freeScreenResources(resources); +} + +static int getWindowMonitorDPI(WindowData* data) +{ + int winX = data->x + data->w / 2; + int winY = data->y + data->w / 2; + // get the DPI from our cached monitor + for (int i = 0; i < s_X11.maxMonitorData; i++) { + if (!s_X11.monitorData->used) { + continue; + } + + // we found a monitor, check the monitor bounds with the window + MonitorData* info = &s_X11.monitorData[i]; + if (winX >= info->x && winX < info->x + info->w && winY >= info->y && + winY < info->y + info->h) { + // found monitor + return info->dpi; + } + } + + return 96; +} + +void sendWMEvent( + Window window, + Atom type, + long a, + long b, + long c, + long d, + PalBool add) +{ + XEvent e = {0}; + e.xclient.type = ClientMessage; + e.xclient.send_event = True; + e.xclient.window = window; + e.xclient.message_type = type; + e.xclient.format = 32; + if (add) { + e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD + } else { + e.xclient.data.l[0] = 0; // _NET_WM_STATE_REMOVE + } + + e.xclient.data.l[1] = a; + e.xclient.data.l[2] = b; + e.xclient.data.l[3] = c; + e.xclient.data.l[4] = d; + + s_X11.sendEvent( + s_X11.display, + s_X11.root, + False, + SubstructureNotifyMask | SubstructureRedirectMask, + &e); +} + +static void createScancodeTable() +{ + // Letters + s_Keyboard.scancodes[0x01E] = PAL_SCANCODE_A; + s_Keyboard.scancodes[0x030] = PAL_SCANCODE_B; + s_Keyboard.scancodes[0x02E] = PAL_SCANCODE_C; + s_Keyboard.scancodes[0x020] = PAL_SCANCODE_D; + s_Keyboard.scancodes[0x012] = PAL_SCANCODE_E; + s_Keyboard.scancodes[0x021] = PAL_SCANCODE_F; + s_Keyboard.scancodes[0x022] = PAL_SCANCODE_G; + s_Keyboard.scancodes[0x023] = PAL_SCANCODE_H; + s_Keyboard.scancodes[0x017] = PAL_SCANCODE_I; + s_Keyboard.scancodes[0x024] = PAL_SCANCODE_J; + s_Keyboard.scancodes[0x025] = PAL_SCANCODE_K; + s_Keyboard.scancodes[0x026] = PAL_SCANCODE_L; + s_Keyboard.scancodes[0x032] = PAL_SCANCODE_M; + s_Keyboard.scancodes[0x031] = PAL_SCANCODE_N; + s_Keyboard.scancodes[0x018] = PAL_SCANCODE_O; + s_Keyboard.scancodes[0x019] = PAL_SCANCODE_P; + s_Keyboard.scancodes[0x010] = PAL_SCANCODE_Q; + s_Keyboard.scancodes[0x013] = PAL_SCANCODE_R; + s_Keyboard.scancodes[0x01F] = PAL_SCANCODE_S; + s_Keyboard.scancodes[0x014] = PAL_SCANCODE_T; + s_Keyboard.scancodes[0x016] = PAL_SCANCODE_U; + s_Keyboard.scancodes[0x02F] = PAL_SCANCODE_V; + s_Keyboard.scancodes[0x011] = PAL_SCANCODE_W; + s_Keyboard.scancodes[0x02D] = PAL_SCANCODE_X; + s_Keyboard.scancodes[0x015] = PAL_SCANCODE_Y; + s_Keyboard.scancodes[0x02C] = PAL_SCANCODE_Z; + + // Numbers (top row) + s_Keyboard.scancodes[0x00B] = PAL_SCANCODE_0; + s_Keyboard.scancodes[0x002] = PAL_SCANCODE_1; + s_Keyboard.scancodes[0x003] = PAL_SCANCODE_2; + s_Keyboard.scancodes[0x004] = PAL_SCANCODE_3; + s_Keyboard.scancodes[0x005] = PAL_SCANCODE_4; + s_Keyboard.scancodes[0x006] = PAL_SCANCODE_5; + s_Keyboard.scancodes[0x007] = PAL_SCANCODE_6; + s_Keyboard.scancodes[0x008] = PAL_SCANCODE_7; + s_Keyboard.scancodes[0x009] = PAL_SCANCODE_8; + s_Keyboard.scancodes[0x00A] = PAL_SCANCODE_9; + + // Function + s_Keyboard.scancodes[0x03B] = PAL_SCANCODE_F1; + s_Keyboard.scancodes[0x03C] = PAL_SCANCODE_F2; + s_Keyboard.scancodes[0x03D] = PAL_SCANCODE_F3; + s_Keyboard.scancodes[0x03E] = PAL_SCANCODE_F4; + s_Keyboard.scancodes[0x03F] = PAL_SCANCODE_F5; + s_Keyboard.scancodes[0x040] = PAL_SCANCODE_F6; + s_Keyboard.scancodes[0x041] = PAL_SCANCODE_F7; + s_Keyboard.scancodes[0x042] = PAL_SCANCODE_F8; + s_Keyboard.scancodes[0x043] = PAL_SCANCODE_F9; + s_Keyboard.scancodes[0x044] = PAL_SCANCODE_F10; + s_Keyboard.scancodes[0x057] = PAL_SCANCODE_F11; + s_Keyboard.scancodes[0x058] = PAL_SCANCODE_F12; + + // Control + s_Keyboard.scancodes[0x001] = PAL_SCANCODE_ESCAPE; + s_Keyboard.scancodes[0x01C] = PAL_SCANCODE_ENTER; + s_Keyboard.scancodes[0x00F] = PAL_SCANCODE_TAB; + s_Keyboard.scancodes[0x00E] = PAL_SCANCODE_BACKSPACE; + s_Keyboard.scancodes[0x039] = PAL_SCANCODE_SPACE; + s_Keyboard.scancodes[0x03A] = PAL_SCANCODE_CAPSLOCK; + s_Keyboard.scancodes[0x045] = PAL_SCANCODE_NUMLOCK; + s_Keyboard.scancodes[0x046] = PAL_SCANCODE_SCROLLLOCK; + s_Keyboard.scancodes[0x02A] = PAL_SCANCODE_LSHIFT; + s_Keyboard.scancodes[0x036] = PAL_SCANCODE_RSHIFT; + s_Keyboard.scancodes[0x01D] = PAL_SCANCODE_LCTRL; + s_Keyboard.scancodes[0x061] = PAL_SCANCODE_RCTRL; + s_Keyboard.scancodes[0x038] = PAL_SCANCODE_LALT; + s_Keyboard.scancodes[0x064] = PAL_SCANCODE_RALT; + + // Arrows + s_Keyboard.scancodes[0x069] = PAL_SCANCODE_LEFT; + s_Keyboard.scancodes[0x06A] = PAL_SCANCODE_RIGHT; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_UP; + s_Keyboard.scancodes[0x06C] = PAL_SCANCODE_DOWN; + + // Navigation + s_Keyboard.scancodes[0x06E] = PAL_SCANCODE_INSERT; + s_Keyboard.scancodes[0x06F] = PAL_SCANCODE_DELETE; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_HOME; + s_Keyboard.scancodes[0x067] = PAL_SCANCODE_END; + s_Keyboard.scancodes[0x068] = PAL_SCANCODE_PAGEUP; + s_Keyboard.scancodes[0x06D] = PAL_SCANCODE_PAGEDOWN; + + // Keypad + s_Keyboard.scancodes[0x052] = PAL_SCANCODE_KP_0; + s_Keyboard.scancodes[0x04F] = PAL_SCANCODE_KP_1; + s_Keyboard.scancodes[0x050] = PAL_SCANCODE_KP_2; + s_Keyboard.scancodes[0x051] = PAL_SCANCODE_KP_3; + s_Keyboard.scancodes[0x04B] = PAL_SCANCODE_KP_4; + s_Keyboard.scancodes[0x04C] = PAL_SCANCODE_KP_5; + s_Keyboard.scancodes[0x04D] = PAL_SCANCODE_KP_6; + s_Keyboard.scancodes[0x047] = PAL_SCANCODE_KP_7; + s_Keyboard.scancodes[0x048] = PAL_SCANCODE_KP_8; + s_Keyboard.scancodes[0x049] = PAL_SCANCODE_KP_9; + s_Keyboard.scancodes[0x060] = PAL_SCANCODE_KP_ENTER; + s_Keyboard.scancodes[0x04E] = PAL_SCANCODE_KP_ADD; + s_Keyboard.scancodes[0x04A] = PAL_SCANCODE_KP_SUBTRACT; + s_Keyboard.scancodes[0x037] = PAL_SCANCODE_KP_MULTIPLY; + s_Keyboard.scancodes[0x062] = PAL_SCANCODE_KP_DIVIDE; + s_Keyboard.scancodes[0x053] = PAL_SCANCODE_KP_DECIMAL; + + // Misc + s_Keyboard.scancodes[0x063] = PAL_SCANCODE_PRINTSCREEN; + s_Keyboard.scancodes[0x066] = PAL_SCANCODE_PAUSE; + s_Keyboard.scancodes[0x07F] = PAL_SCANCODE_MENU; + s_Keyboard.scancodes[0x028] = PAL_SCANCODE_APOSTROPHE; + s_Keyboard.scancodes[0x02B] = PAL_SCANCODE_BACKSLASH; + s_Keyboard.scancodes[0x033] = PAL_SCANCODE_COMMA; + s_Keyboard.scancodes[0x00D] = PAL_SCANCODE_EQUAL; + s_Keyboard.scancodes[0x029] = PAL_SCANCODE_GRAVEACCENT; + s_Keyboard.scancodes[0x00C] = PAL_SCANCODE_SUBTRACT; + s_Keyboard.scancodes[0x034] = PAL_SCANCODE_PERIOD; + s_Keyboard.scancodes[0x027] = PAL_SCANCODE_SEMICOLON; + s_Keyboard.scancodes[0x035] = PAL_SCANCODE_SLASH; + s_Keyboard.scancodes[0x01A] = PAL_SCANCODE_LBRACKET; + s_Keyboard.scancodes[0x01B] = PAL_SCANCODE_RBRACKET; + s_Keyboard.scancodes[0x07D] = PAL_SCANCODE_LSUPER; + s_Keyboard.scancodes[0x07E] = PAL_SCANCODE_RSUPER; +} + +static void createKeycodeTable() +{ + // Tis is for only printable and text input keys + + // Letters + s_Keyboard.keycodes[XK_a] = PAL_KEYCODE_A; + s_Keyboard.keycodes[XK_b] = PAL_KEYCODE_B; + s_Keyboard.keycodes[XK_c] = PAL_KEYCODE_C; + s_Keyboard.keycodes[XK_d] = PAL_KEYCODE_D; + s_Keyboard.keycodes[XK_e] = PAL_KEYCODE_E; + s_Keyboard.keycodes[XK_f] = PAL_KEYCODE_F; + s_Keyboard.keycodes[XK_g] = PAL_KEYCODE_G; + s_Keyboard.keycodes[XK_h] = PAL_KEYCODE_H; + s_Keyboard.keycodes[XK_i] = PAL_KEYCODE_I; + s_Keyboard.keycodes[XK_j] = PAL_KEYCODE_J; + s_Keyboard.keycodes[XK_k] = PAL_KEYCODE_K; + s_Keyboard.keycodes[XK_l] = PAL_KEYCODE_L; + s_Keyboard.keycodes[XK_m] = PAL_KEYCODE_M; + s_Keyboard.keycodes[XK_n] = PAL_KEYCODE_N; + s_Keyboard.keycodes[XK_o] = PAL_KEYCODE_O; + s_Keyboard.keycodes[XK_p] = PAL_KEYCODE_P; + s_Keyboard.keycodes[XK_q] = PAL_KEYCODE_Q; + s_Keyboard.keycodes[XK_r] = PAL_KEYCODE_R; + s_Keyboard.keycodes[XK_s] = PAL_KEYCODE_S; + s_Keyboard.keycodes[XK_t] = PAL_KEYCODE_T; + s_Keyboard.keycodes[XK_u] = PAL_KEYCODE_U; + s_Keyboard.keycodes[XK_v] = PAL_KEYCODE_V; + s_Keyboard.keycodes[XK_w] = PAL_KEYCODE_W; + s_Keyboard.keycodes[XK_x] = PAL_KEYCODE_X; + s_Keyboard.keycodes[XK_y] = PAL_KEYCODE_Y; + s_Keyboard.keycodes[XK_z] = PAL_KEYCODE_Z; + + // Control + s_Keyboard.keycodes[XK_space] = PAL_KEYCODE_SPACE; + + // Misc + s_Keyboard.keycodes[XK_apostrophe] = PAL_KEYCODE_APOSTROPHE; + s_Keyboard.keycodes[XK_backslash] = PAL_KEYCODE_BACKSLASH; + s_Keyboard.keycodes[XK_comma] = PAL_KEYCODE_COMMA; + s_Keyboard.keycodes[XK_equal] = PAL_KEYCODE_EQUAL; + s_Keyboard.keycodes[XK_grave] = PAL_KEYCODE_GRAVEACCENT; + s_Keyboard.keycodes[XK_minus] = PAL_KEYCODE_SUBTRACT; + s_Keyboard.keycodes[XK_period] = PAL_KEYCODE_PERIOD; + s_Keyboard.keycodes[XK_semicolon] = PAL_KEYCODE_SEMICOLON; + s_Keyboard.keycodes[XK_slash] = PAL_KEYCODE_SLASH; + s_Keyboard.keycodes[XK_bracketleft] = PAL_KEYCODE_LBRACKET; + s_Keyboard.keycodes[XK_bracketright] = PAL_KEYCODE_RBRACKET; +} + +MonitorData* xGetFreeMonitorData() +{ + for (int i = 0; i < s_X11.maxMonitorData; ++i) { + if (!s_X11.monitorData[i].used) { + s_X11.monitorData[i].used = PAL_TRUE; + return &s_X11.monitorData[i]; + } + } + + // resize the data array + // this will almost not reach here since most setups are 1-4 monitors + MonitorData* data = nullptr; + int count = s_X11.maxMonitorData * 2; // double the size + int freeIndex = s_X11.maxMonitorData + 1; + data = palAllocate(s_X11.allocator, sizeof(MonitorData) * count, 0); + if (data) { + memcpy(data, s_X11.monitorData, s_X11.maxMonitorData * sizeof(MonitorData)); + + palFree(s_X11.allocator, s_X11.monitorData); + s_X11.monitorData = data; + s_X11.maxWindowData = count; + + s_X11.monitorData[freeIndex].used = PAL_TRUE; + return &s_X11.monitorData[freeIndex]; + } + return nullptr; +} + +MonitorData* xFindMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_X11.maxMonitorData; ++i) { + if (s_X11.monitorData[i].used && s_X11.monitorData[i].monitor == monitor) { + return &s_X11.monitorData[i]; + } + } + return nullptr; +} + +void xFreeMonitorData(PalMonitor* monitor) +{ + for (int i = 0; i < s_X11.maxMonitorData; ++i) { + if (s_X11.monitorData[i].used && s_X11.monitorData[i].monitor == monitor) { + s_X11.monitorData[i].used = PAL_FALSE; + } + } +} + +WindowData* xGetFreeWindowData() +{ + for (int i = 0; i < s_X11.maxWindowData; ++i) { + if (!s_X11.windowData[i].used) { + s_X11.windowData[i].used = PAL_TRUE; + return &s_X11.windowData[i]; + } + } + + // resize the data array + // It is rare for a user to create and manage + // 32 windows at the same time + WindowData* data = nullptr; + int count = s_X11.maxWindowData * 2; // double the size + int freeIndex = s_X11.maxWindowData + 1; + data = palAllocate(s_X11.allocator, sizeof(WindowData) * count, 0); + if (data) { + memcpy(data, s_X11.windowData, s_X11.maxWindowData * sizeof(WindowData)); + + palFree(s_X11.allocator, s_X11.windowData); + s_X11.windowData = data; + s_X11.maxWindowData = count; + + s_X11.windowData[freeIndex].used = PAL_TRUE; + return &s_X11.windowData[freeIndex]; + } + return nullptr; +} + +WindowData* xFindWindowData(PalWindow* window) +{ + for (int i = 0; i < s_X11.maxWindowData; ++i) { + if (s_X11.windowData[i].used && s_X11.windowData[i].window == window) { + return &s_X11.windowData[i]; + } + } + return nullptr; +} + +PalResult xInitVideo( + const PalAllocator* allocator, + PalEventDriver* eventDriver, + void* preferredInstance) +{ + // load X11 dependencies + s_X11.handle = dlopen("libX11.so", RTLD_LAZY); + s_X11.libCursor = dlopen("libXcursor.so", RTLD_LAZY); + if (!s_X11.handle || !s_X11.libCursor) { + return palMakeResult(PAL_RESULT_CODE_PLATFORM_FAILURE, PAL_RESULT_SOURCE_POSIX, errno); + } + + s_X11.xrandr = dlopen("libXrandr.so.2", RTLD_LAZY); + if (!s_X11.xrandr) { + s_X11.xrandr = dlopen("libXrandr.so", RTLD_LAZY); + } + + // clang-format off + // load procs + s_X11.openDisplay = (XOpenDisplayFn)dlsym( + s_X11.handle, + "XOpenDisplay"); + + s_X11.closeDisplay = (XCloseDisplayFn)dlsym( + s_X11.handle, + "XCloseDisplay"); + + s_X11.getWindowAttributes = (XGetWindowAttributesFn)dlsym( + s_X11.handle, + "XGetWindowAttributes"); + + s_X11.setCrtcConfig = (XRRSetCrtcConfigFn)dlsym( + s_X11.handle, + "XRRSetCrtcConfig"); + + s_X11.getWindowProperty = (XGetWindowPropertyFn)dlsym( + s_X11.handle, + "XGetWindowProperty"); + + s_X11.internAtom = (XInternAtomFn)dlsym( + s_X11.handle, + "XInternAtom"); + + s_X11.getSelectionOwner = (XGetSelectionOwnerFn)dlsym( + s_X11.handle, + "XGetSelectionOwner"); + + s_X11.freeColormap = (XFreeColormapFn)dlsym( + s_X11.handle, + "XFreeColormap"); + + s_X11.storeName = (XStoreNameFn)dlsym( + s_X11.handle, + "XStoreName"); + + s_X11.changeProperty = (XChangePropertyFn)dlsym( + s_X11.handle, + "XChangeProperty"); + + s_X11.flush = (XFlushFn)dlsym( + s_X11.handle, + "XFlush"); + + s_X11.createColormap = (XCreateColormapFn)dlsym( + s_X11.handle, + "XCreateColormap"); + + s_X11.mapWindow = (XMapWindowFn)dlsym( + s_X11.handle, + "XMapWindow"); + + s_X11.unmapWindow = (XUnmapWindowFn)dlsym( + s_X11.handle, + "XUnmapWindow"); + + s_X11.createWindow = (XCreateWindowFn)dlsym( + s_X11.handle, + "XCreateWindow"); + + s_X11.destroyWindow = (XDestroyWindowFn)dlsym( + s_X11.handle, + "XDestroyWindow"); + + s_X11.matchVisualInfo = (XMatchVisualInfoFn)dlsym( + s_X11.handle, + "XMatchVisualInfo"); + + s_X11.pending = (XPendingFn)dlsym( + s_X11.handle, + "XPending"); + + s_X11.setWMProtocols = (XSetWMProtocolsFn)dlsym( + s_X11.handle, + "XSetWMProtocols"); + + s_X11.nextEvent = (XNextEventFn)dlsym( + s_X11.handle, + "XNextEvent"); + + s_X11.setWMNormalHints = (XSetWMNormalHintsFn)dlsym( + s_X11.handle, + "XSetWMNormalHints"); + + s_X11.getWMNormalHints = (XGetWMNormalHintsFn)dlsym( + s_X11.handle, + "XGetWMNormalHints"); + + s_X11.sendEvent = (XSendEventFn)dlsym( + s_X11.handle, + "XSendEvent"); + + s_X11.moveWindow = (XMoveWindowFn)dlsym( + s_X11.handle, + "XMoveWindow"); + + s_X11.resizeWindow = (XResizeWindowFn)dlsym( + s_X11.handle, + "XResizeWindow"); + + s_X11.iconifyWindow = (XIconifyWindowFn)dlsym( + s_X11.handle, + "XIconifyWindow"); + + s_X11.setErrorHandler = (XSetErrorHandlerFn)dlsym( + s_X11.handle, + "XSetErrorHandler"); + + s_X11.sync = (XSyncFn)dlsym( + s_X11.handle, + "XSync"); + + s_X11.saveContext = (XSaveContextFn)dlsym( + s_X11.handle, + "XSaveContext"); + + s_X11.findContext = (XFindContextFn)dlsym( + s_X11.handle, + "XFindContext"); + + s_X11.uniqueContext = (XrmUniqueQuarkFn)dlsym( + s_X11.handle, + "XrmUniqueQuark"); + + // load Xrandr functions + s_X11.getScreenResources = (XRRGetScreenResourcesFn)dlsym( + s_X11.xrandr, + "XRRGetScreenResources"); + + s_X11.getOutputPrimary = (XRRGetOutputPrimaryFn)dlsym( + s_X11.xrandr, + "XRRGetOutputPrimary"); + + s_X11.getOutputInfo = (XRRGetOutputInfoFn)dlsym( + s_X11.xrandr, + "XRRGetOutputInfo"); + + s_X11.getCrtcInfo = (XRRGetCrtcInfoFn)dlsym( + s_X11.xrandr, + "XRRGetCrtcInfo"); + + s_X11.freeScreenResources = (XRRFreeScreenResourcesFn)dlsym( + s_X11.xrandr, + "XRRFreeScreenResources"); + + s_X11.freeOutputInfo = (XRRFreeOutputInfoFn)dlsym( + s_X11.xrandr, + "XRRFreeScreenResources"); + + s_X11.freeCrtcInfo = (XRRFreeCrtcInfoFn)dlsym( + s_X11.xrandr, + "XRRFreeCrtcInfo"); + + s_X11.selectRRInput = (XRRSelectInputFn)dlsym( + s_X11.xrandr, + "XRRSelectInput"); + + s_X11.queryRRExtension = (XRRQueryExtensionFn)dlsym( + s_X11.xrandr, + "XRRQueryExtension"); + + s_X11.allocClassHint = (XAllocClassHintFn)dlsym( + s_X11.handle, + "XAllocClassHint"); + + s_X11.setClassHint = (XSetClassHintFn)dlsym( + s_X11.handle, + "XSetClassHint"); + + s_X11.free = (XFreeFn)dlsym( + s_X11.handle, + "XFree"); + + s_X11.getVisualInfo = (XGetVisualInfoFn)dlsym( + s_X11.handle, + "XGetVisualInfo"); + + s_X11.createFontCursor = (XCreateFontCursorFn)dlsym( + s_X11.handle, + "XCreateFontCursor"); + + s_X11.freePixmap = (XFreePixmapFn)dlsym( + s_X11.handle, + "XFreePixmap"); + + s_X11.setWMHints = (XSetWMHintsFn)dlsym( + s_X11.handle, + "XSetWMHints"); + + s_X11.grabPointer = (XGrabPointerFn)dlsym( + s_X11.handle, + "XGrabPointer"); + + s_X11.createPixmapCursor = (XCreatePixmapCursorFn)dlsym( + s_X11.handle, + "XCreatePixmapCursor"); + + s_X11.warpPointer = (XWarpPointerFn)dlsym( + s_X11.handle, + "XWarpPointer"); + + s_X11.getWMName = (XGetWMNameFn)dlsym( + s_X11.handle, + "XGetWMName"); + + s_X11.queryPointer = (XQueryPointerFn)dlsym( + s_X11.handle, + "XQueryPointer"); + + s_X11.ungrabPointer = (XUngrabPointerFn)dlsym( + s_X11.handle, + "XUngrabPointer"); + + s_X11.allocWMHints = (XAllocWMHintsFn)dlsym( + s_X11.handle, + "XAllocWMHints"); + + s_X11.mapRaised = (XMapRaisedFn)dlsym( + s_X11.handle, + "XMapRaised"); + + s_X11.undefineCursor = (XUndefineCursorFn)dlsym( + s_X11.handle, + "XUndefineCursor"); + + s_X11.defineCursor = (XDefineCursorFn)dlsym( + s_X11.handle, + "XDefineCursor"); + + s_X11.freeCursor = (XFreeCursorFn)dlsym( + s_X11.handle, + "XFreeCursor"); + + s_X11.getWMHints = (XGetWMHintsFn)dlsym( + s_X11.handle, + "XGetWMHints"); + + s_X11.createPixmap = (XCreatePixmapFn)dlsym( + s_X11.handle, + "XCreatePixmap"); + + s_X11.setInputFocus = (XSetInputFocusFn)dlsym( + s_X11.handle, + "XSetInputFocus"); + + s_X11.getInputFocus = (XGetInputFocusFn)dlsym( + s_X11.handle, + "XGetInputFocus"); + + s_X11.selectInput = (XSelectInputFn)dlsym( + s_X11.handle, + "XSelectInput"); + + // libXcursor + s_X11.cursorImageLoadCursor = (XcursorImageLoadCursorFn)dlsym( + s_X11.libCursor, + "XcursorImageLoadCursor"); + + s_X11.cursorImageCreate = (XcursorImageCreateFn)dlsym( + s_X11.libCursor, + "XcursorImageCreate"); + + s_X11.cursorImageDestroy = (XcursorImageDestroyFn)dlsym( + s_X11.libCursor, + "XcursorImageDestroy"); + + s_X11.lookupKeysym = (XLookupKeysymFn)dlsym( + s_X11.libCursor, + "XLookupKeysym"); + + s_X11.setDetectableAutoRepeat = (XkbSetDetectableAutoRepeatFn)dlsym( + s_X11.handle, + "XkbSetDetectableAutoRepeat"); + + s_X11.setLocaleModifiers = (XSetLocaleModifiersFn)dlsym( + s_X11.handle, + "XSetLocaleModifiers"); + + s_X11.openIM = (XOpenIMFn)dlsym( + s_X11.handle, + "XOpenIM"); + + s_X11.closeIM = (XCloseIMFn)dlsym( + s_X11.handle, + "XCloseIM"); + + s_X11.createIC = (XCreateICFn)dlsym( + s_X11.handle, + "XCreateIC"); + + s_X11.destroyIC = (XDestroyICFn)dlsym( + s_X11.handle, + "XDestroyIC"); + + s_X11.utf8LookupString = (Xutf8LookupStringFn)dlsym( + s_X11.handle, + "Xutf8LookupString"); + // clang-format on + + s_X11.maxMonitorData = 16; // initial size + s_X11.maxWindowData = 32; // initial size + s_X11.windowData = palAllocate(s_X11.allocator, sizeof(WindowData) * s_X11.maxWindowData, 0); + s_X11.monitorData = palAllocate(s_X11.allocator, sizeof(MonitorData) * s_X11.maxMonitorData, 0); + if (!s_X11.monitorData || !s_X11.windowData) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + // X11 server + if (preferredInstance) { + s_X11.display = (Display*)preferredInstance; + + } else { + s_X11.display = s_X11.openDisplay(nullptr); + if (!s_X11.display) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + } + + s_X11.root = DefaultRootWindow(s_X11.display); + s_X11.screen = DefaultScreen(s_X11.display); + checkFeatures(); + + // subscribe for monitor events + s_X11.selectRRInput(s_X11.display, s_X11.root, RRScreenChangeNotifyMask | RRNotify); + int eventBase, errorBase = 0; + s_X11.queryRRExtension(s_X11.display, &eventBase, &errorBase); + s_X11.rrEventBase = eventBase; + s_X11.skipScreenEvent = PAL_TRUE; + + s_X11.dataID = (XContext)s_X11.uniqueContext(); + resetMonitorData(); + + s_X11.monitorCount = 0; + cacheMonitors(); + + // we load GLX + s_X11.glxHandle = dlopen("libGL.so.1", RTLD_LAZY); + if (s_X11.glxHandle) { + GLXGetProcAddressFn loadFunc = nullptr; + loadFunc = (GLXGetProcAddressFn)dlsym(s_X11.glxHandle, "glXGetProcAddress"); + s_X11.glxGetFBConfigs = + (GLXGetFBConfigsFn)loadFunc((const unsigned char*)"glXGetFBConfigs"); + + s_X11.glxGetFBConfigAttrib = + (GLXGetFBConfigAttribFn)loadFunc((const unsigned char*)"glXGetFBConfigAttrib"); + + s_X11.glxGetVisualFromFBConfig = + (GLXGetVisualFromFBConfigFn)loadFunc((const unsigned char*)"glXGetVisualFromFBConfig"); + } + + // load EGL + s_VideoEgl.handle = dlopen("libEGL.so", RTLD_LAZY); + if (s_VideoEgl.handle) { + eglGetProcAddressFn load = nullptr; + load = (eglGetProcAddressFn)dlsym(s_VideoEgl.handle, "eglGetProcAddress"); + + s_VideoEgl.initialize = (eglInitializeFn)load("eglInitialize"); + s_VideoEgl.terminate = (eglTerminateFn)load("eglTerminate"); + s_VideoEgl.getDisplay = (eglGetDisplayFn)load("eglGetDisplay"); + s_VideoEgl.chooseConfig = (eglChooseConfigFn)load("eglChooseConfig"); + s_VideoEgl.getError = (eglGetErrorFn)load("eglGetError"); + s_VideoEgl.bindAPI = (eglBindAPIFn)load("eglBindAPI"); + s_VideoEgl.getConfigs = (eglGetConfigsFn)load("eglGetConfigs"); + s_VideoEgl.getConfigAttrib = (eglGetConfigAttribFn)load("eglGetConfigAttrib"); + } + + createKeycodeTable(); + createScancodeTable(); + + // disable auto key repeats + int supported; + s_X11.setDetectableAutoRepeat(s_X11.display, True, &supported); + + // create an input method + s_X11.setLocaleModifiers(""); + s_X11.im = s_X11.openIM(s_X11.display, nullptr, nullptr, nullptr); + if (s_X11.im == None) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + s_X11.allocator = allocator; + s_X11.eventDriver = eventDriver; + return PAL_RESULT_SUCCESS; +} + +void xShutdownVideo() +{ + s_X11.closeIM(s_X11.im); + if (!s_X11.display) { + // opened by PAL + s_X11.closeDisplay(s_X11.display); + + dlclose(s_X11.handle); + dlclose(s_X11.xrandr); + dlclose(s_X11.libCursor); + } + + palFree(s_X11.allocator, s_X11.windowData); + palFree(s_X11.allocator, s_X11.monitorData); + if (s_VideoEgl.handle) { + dlclose(s_VideoEgl.handle); + } + + if (s_X11.glxHandle) { + dlclose(s_X11.glxHandle); + } + + memset(&s_X11, 0, sizeof(X11)); + memset(&s_X11Atoms, 0, sizeof(X11Atoms)); +} + +void xUpdateVideo() +{ + XEvent event; + PalDispatchMode mode = PAL_DISPATCH_MODE_NONE; + while (s_X11.pending(s_X11.display)) { + s_X11.nextEvent(s_X11.display, &event); + + Window xWin = event.xany.window; + PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + + if (event.type == s_X11.rrEventBase + RRScreenChangeNotify) { + event.type = RANDR_SCREEN_CHANGE_EVENT; // for switch flow + } + + switch (event.type) { + case ClientMessage: { + // check for window close + Atom windowClose = event.xclient.data.l[0]; + if (windowClose == s_X11Atoms.WM_DELETE_WINDOW) { + if (s_X11.eventDriver) { + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_CLOSE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + break; + } + + case ConfigureNotify: { + // window resize or move + + // skip the first configure event + if (data->skipConfigure) { + data->skipConfigure = PAL_FALSE; + data->w = event.xconfigure.width; + data->h = event.xconfigure.height; + data->x = event.xconfigure.x; + data->y = event.xconfigure.y; + break; + } + + // real configure event + if (s_X11.eventDriver) { + // check if its a resize event + if (data->w != event.xconfigure.width || data->h != event.xconfigure.height) { + data->w = event.xconfigure.width; + data->h = event.xconfigure.height; + + // push a resize event + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_SIZE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(data->w, data->h); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + // attach windows sometimes bypass + // skipConfgure an still send an initial move event + if (data->isAttached) { + if (data->skipIfAttached) { + data->skipIfAttached = PAL_FALSE; + break; + } + } + + // check if its a move event + if (data->x != event.xconfigure.x || data->y != event.xconfigure.y) { + data->x = event.xconfigure.x; + data->y = event.xconfigure.y; + + // push a move event + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_MOVE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(data->x, data->y); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + /** a window has to be moved + before its can change monitors + we get the monitor the moved + window is on and check if the dpi is different + from the one it was created on */ + int monitorDPI = getWindowMonitorDPI(data); + if (monitorDPI != data->dpi) { + // window is on a different monitor + data->dpi = monitorDPI; + + // push a DPI event + type = PAL_EVENT_TYPE_MONITOR_DPI_CHANGED; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = monitorDPI; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + } + break; + } + + case FocusIn: { + // window has gained focus + if (s_X11.eventDriver) { + int mode = event.xfocus.mode; + if (mode == NotifyGrab || mode == NotifyUngrab) { + // ignore dragging and popup focus events + break; + } + + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = PAL_TRUE; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + + case FocusOut: { + // window has lost focus + if (s_X11.eventDriver) { + int mode = event.xfocus.mode; + if (mode == NotifyGrab || mode == NotifyUngrab) { + // ignore dragging and popup focus events + break; + } + + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_FOCUS; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = PAL_FALSE; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + + case PropertyNotify: { + // check window state (maximize, minimize) + if (event.xproperty.atom == s_X11Atoms._NET_WM_STATE) { + PalWindowState state; + state = queryWindowState(event.xproperty.window); + if (state != data->state) { + data->state = state; + + // skip the first state event + if (data->skipState) { + data->skipState = PAL_FALSE; + break; + } + + // push event + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_WINDOW_STATE; + mode = palGetEventDispatchMode(driver, type); + + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = data->state; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + break; + } + + case RANDR_SCREEN_CHANGE_EVENT: { + // skip the first event + if (s_X11.skipScreenEvent) { + s_X11.skipScreenEvent = PAL_FALSE; + break; + } + + // store old monitor count + int oldCount = s_X11.monitorCount; + s_X11.monitorCount = 0; + cacheMonitors(); + + if (oldCount != s_X11.monitorCount) { + // a monitor has been added or removed + if (s_X11.eventDriver) { + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MONITOR_LIST_CHANGED; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + } + break; + } + + case MotionNotify: { + // mouse moved + const int x = event.xmotion.x; + const int y = event.xmotion.y; + const int dx = x - s_Mouse.lastX; + const int dy = y - s_Mouse.lastY; + + if (s_X11.eventDriver) { + PalEventDriver* driver = s_X11.eventDriver; + PalEventType type = PAL_EVENT_TYPE_MOUSE_MOVE; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackInt32(x, y); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + // push a mouse delta event + type = PAL_EVENT_TYPE_MOUSE_DELTA; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackFloat((float)dx, (float)dy); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + s_Mouse.lastX = x; + s_Mouse.lastY = y; + s_Mouse.dx = dx; + s_Mouse.dy = dy; + break; + } + + case ButtonPress: + case ButtonRelease: { + int xButton = event.xbutton.button; + PalBool pressed = (event.xbutton.type == ButtonPress); + PalMouseButton button = 0; + PalEventType type; + + if (xButton == 1) { + button = PAL_MOUSE_BUTTON_LEFT; + } else if (xButton == 3) { + button = PAL_MOUSE_BUTTON_RIGHT; + } else if (xButton == 2) { + button = PAL_MOUSE_BUTTON_MIDDLE; + } + + s_Mouse.state[button] = pressed; + if (s_X11.eventDriver && button != 0) { + PalEventDriver* driver = s_X11.eventDriver; + if (pressed) { + type = PAL_EVENT_TYPE_MOUSE_BUTTONDOWN; + } else { + type = PAL_EVENT_TYPE_MOUSE_BUTTONUP; + } + + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(button, NULL_BUTTON_SERIAL); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + + int scrollX = 0; + int scrollY = 0; + if (xButton == 4) { + // scroll up + scrollY = 1; + } else if (xButton == 5) { + // scroll down + scrollY = -1; + } else if (xButton == 6) { + // scroll left + scrollX = -1; + } else if (xButton == 7) { + // scroll right + scrollX = 1; + } + + s_Mouse.WheelX = scrollX; + s_Mouse.WheelY = scrollY; + if (s_X11.eventDriver && (scrollX || scrollY)) { + PalEventDriver* driver = s_X11.eventDriver; + mode = palGetEventDispatchMode(driver, PAL_EVENT_TYPE_MOUSE_WHEEL); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = PAL_EVENT_TYPE_MOUSE_WHEEL; + event.data = palPackFloat((float)scrollX, (float)scrollY); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + + case KeyPress: + case KeyRelease: { + int xScancode = event.xkey.keycode; + PalBool pressed = (event.xbutton.type == KeyPress); + PalScancode scancode = PAL_SCANCODE_UNKNOWN; + PalKeycode keycode = PAL_KEYCODE_UNKNOWN; + PalEventType type; + KeySym keySym = s_X11.lookupKeysym(&event.xkey, 0); + + // special handling for Pause/break with Home + if (xScancode == 110) { + if (keySym == XK_Pause) { + scancode = PAL_SCANCODE_PAUSE; + } else { + scancode = PAL_SCANCODE_HOME; + } + + } else { + int index = xScancode - 8; + scancode = s_Keyboard.scancodes[index]; + } + + // printable and text input keys are from the range + // 32 (PAL_KEYCODE_SPACE) and 122 (PAL_KEYCODE_Z) + // The rest are almost the same as their scancode + // Maybe there will be a layout that makes this wrong + // but for now this works + if (keySym >= XK_space && keySym <= XK_z) { + // a printable or input key + keycode = s_Keyboard.keycodes[keySym]; + + } else { + // Since PalKeycode and PalScancode have the same integers + // we can make a direct cast without a table + // Examle: PAL_KEYCODE_A(int 0) == PAL_SCANCODE_A(int 0) + keycode = (PalKeycode)(uint32_t)scancode; + } + + // If we got a keySym but its not mapped into our keycode array + // we do a direct cast as well + if (keycode == PAL_KEYCODE_UNKNOWN) { + keycode = (PalKeycode)(uint32_t)scancode; + } + + // update our keyboard and mouse state to handle key repeat + PalBool repeat = s_Keyboard.keycodeState[keycode]; + s_Keyboard.scancodeState[scancode] = pressed; + s_Keyboard.keycodeState[keycode] = pressed; + + if (pressed) { + if (repeat) { + type = PAL_EVENT_TYPE_KEYREPEAT; + } else { + type = PAL_EVENT_TYPE_KEYDOWN; + } + } else { + type = PAL_EVENT_TYPE_KEYUP; + } + + if (s_X11.eventDriver) { + PalEventDriver* driver = s_X11.eventDriver; + mode = palGetEventDispatchMode(driver, type); + if (mode != PAL_DISPATCH_MODE_NONE) { + PalEvent event = {0}; + event.type = type; + event.data = palPackUint32(keycode, scancode); + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + + // check for char event if enabled + type = PAL_EVENT_TYPE_KEYCHAR; + mode = palGetEventDispatchMode(driver, type); + if (mode == PAL_DISPATCH_MODE_NONE) { + break; + } + + int status; + char buffer[32]; + KeySym keySym; + int len = s_X11.utf8LookupString( + data->ic, + &event.xkey, + buffer, + sizeof(buffer), + &keySym, + &status); + + uint32_t codepoint = 0; + if (status == XLookupChars || status == XLookupBoth) { + // decode to Unicode codepoint + unsigned char ch = buffer[0]; + if (ch < 0x80) { + // 1 byte (A-Z) + codepoint = ch; + + } else if ((ch >> 5) == 0x6 && len >= 2) { + // 2 byte + codepoint = ((ch & 0x1F) << 6) | buffer[1] & 0x3F; + + } else if ((ch >> 4) == 0xE && len >= 3) { + // 3 byte + // clang-format off + codepoint = ((ch & 0x0F) << 12) | + ((buffer[1] & 0x3F) << 6) | + (buffer[2] & 0x3F); + // clang-format on + + } else if ((ch >> 3) == 0x1E && len >= 4) { + // 4 byte + // clang-format off + codepoint = ((ch & 0x07) << 18) | + ((buffer[1] & 0x3F) << 12) | + ((buffer[2] & 0x3F) << 6) | + (buffer[3] & 0x3F); + // clang-format on + } + + PalEvent event = {0}; + event.type = type; + event.data = codepoint; + event.data2 = palPackPointer(window); + palPushEvent(driver, &event); + } + } + break; + } + } + } + + s_X11.flush(s_X11.display); +} + +PalVideoFeatures xGetVideoFeatures() +{ + return s_X11.features; +} + +const PalBool* xGetKeycodeState() +{ + return s_Keyboard.keycodeState; +} + +const PalBool* xGetScancodeState() +{ + return s_Keyboard.scancodeState; +} + +const PalBool* xGetMouseState() +{ + return s_Mouse.state; +} + +void xGetMouseDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.dx; + } + + if (dy) { + *dy = (float)s_Mouse.dy; + } +} + +void xGetMouseWheelDelta( + float* dx, + float* dy) +{ + if (dx) { + *dx = (float)s_Mouse.WheelX; + } + + if (dy) { + *dy = (float)s_Mouse.WheelY; + } +} + +void* xGetInstance() +{ + return (void*)s_X11.display; +} + +#endif // PAL_HAS_X11_BACKEND \ No newline at end of file diff --git a/src/video/x11/pal_window_x11.c b/src/video/x11/pal_window_x11.c new file mode 100644 index 00000000..5f5ad698 --- /dev/null +++ b/src/video/x11/pal_window_x11.c @@ -0,0 +1,937 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#if PAL_HAS_X11_BACKEND == 1 +#include "pal_x11.h" +#include +#include +#include +#include + +static int xErrorHandler( + Display* display, + XErrorEvent* e) +{ + // this is use for simple success and failure + s_X11.error = PAL_TRUE; + return 0; +} + +static XVisualInfo* glxBackend(const int fbConfigIndex) +{ + int count = 0; + GLXFBConfig* configs = s_X11.glxGetFBConfigs(s_X11.display, s_X11.screen, &count); + GLXFBConfig fbConfig = configs[fbConfigIndex]; + if (!fbConfig) { + return nullptr; + } + + // get a matching visual + XVisualInfo* visualInfo = s_X11.glxGetVisualFromFBConfig(s_X11.display, fbConfig); + if (!visualInfo) { + return nullptr; + } + + return visualInfo; +} + +static XVisualInfo* eglXBackend(int fbConfigIndex) +{ + EGLDisplay display = EGL_NO_DISPLAY; + display = s_VideoEgl.getDisplay((EGLNativeDisplayType)s_X11.display); + if (display == EGL_NO_DISPLAY) { + return nullptr; + } + + EGLint numConfigs = 0; + if (!s_VideoEgl.getConfigs(display, nullptr, 0, &numConfigs)) { + return nullptr; + } + + EGLint configSize = sizeof(EGLConfig) * numConfigs; + EGLConfig* eglConfigs = palAllocate(s_X11.allocator, configSize, 0); + if (!eglConfigs) { + return nullptr; + } + + s_VideoEgl.getConfigs(display, eglConfigs, numConfigs, &numConfigs); + EGLConfig config = eglConfigs[fbConfigIndex]; + + // we get a visual info from the config + EGLint visualID; + s_VideoEgl.getConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &visualID); + if (visualID == 0) { + return nullptr; + } + + int numVisuals = 0; + XVisualInfo tmp; + tmp.visualid = visualID; + + // get a matching visual info + XVisualInfo* visualInfo = s_X11.getVisualInfo(s_X11.display, VisualIDMask, &tmp, &numVisuals); + if (!visualInfo) { + return nullptr; + } + + palFree(s_X11.allocator, eglConfigs); + return visualInfo; +} + +void xGetMonitorInfo( + PalMonitor*, + PalMonitorInfo*); + +PalResult xCreateWindow( + const PalWindowCreateInfo* info, + PalWindow** outWindow) +{ + Window window = None; + PalMonitor* monitor = nullptr; + PalMonitorInfo monitorInfo; + + WindowData* data = xGetFreeWindowData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + Visual* visual = nullptr; + int depth = 0; + Colormap colormap = None; + unsigned long bgPixel = 0; + unsigned long borderPixel = 0; + + // user provided a config id + if (info->fbConfigIndex) { + PalFBConfigBackend backend = info->fbConfigBackend; + XVisualInfo* visualInfo = nullptr; + + if (backend == PAL_FBCONFIG_BACKEND_PAL_OPENGL) { + backend = PAL_FBCONFIG_BACKEND_EGL; + } + + if (backend == PAL_FBCONFIG_BACKEND_EGL) { + visualInfo = eglXBackend(info->fbConfigIndex); + + } else if (backend == PAL_FBCONFIG_BACKEND_GLX) { + visualInfo = glxBackend(info->fbConfigIndex); + + } else { + return PAL_RESULT_CODE_INVALID_ARGUMENT; + } + + // create a colormap from the visual info + visual = visualInfo->visual; + depth = visualInfo->depth; + bgPixel = 0; + borderPixel = 0; + colormap = s_X11.createColormap(s_X11.display, s_X11.root, visual, AllocNone); + if (!colormap) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + data->colormap = colormap; + + } else { + // use a default visual + visual = DefaultVisual(s_X11.display, s_X11.screen); + depth = DefaultDepth(s_X11.display, s_X11.screen); + bgPixel = WhitePixel(s_X11.display, s_X11.screen); + borderPixel = BlackPixel(s_X11.display, s_X11.screen); + colormap = DefaultColormap(s_X11.display, s_X11.screen); + data->colormap = None; + } + + // get monitor + int monitorX = 0; + int monitorY = 0; + uint32_t monitorW = 0; + uint32_t monitorH = 0; + int dpi = 0; + if (info->monitor) { + monitor = info->monitor; + + } else { + // get primary monitor + xGetPrimaryMonitor(&monitor); + } + + if (monitor) { + // get monitor info + xGetMonitorInfo(monitor, &monitorInfo); + monitorX = monitorInfo.x; + monitorY = monitorInfo.y; + monitorW = monitorInfo.width; + monitorH = monitorInfo.height; + dpi = monitorInfo.dpi; + + } else { + // primary monitor is not set + XRRScreenResources* resources = nullptr; + resources = s_X11.getScreenResources(s_X11.display, s_X11.root); + for (int i = 0; i < resources->noutput; ++i) { + RROutput output = resources->outputs[i]; + XRROutputInfo* outputInfo = s_X11.getOutputInfo(s_X11.display, resources, output); + + // check if its a monitor + if (outputInfo->connection != RR_Connected || outputInfo->crtc == None) { + s_X11.freeOutputInfo(outputInfo); + continue; + } + + XRRCrtcInfo* crtc = s_X11.getCrtcInfo(s_X11.display, resources, outputInfo->crtc); + monitorX = crtc->x; + monitorY = crtc->y; + monitorW = crtc->width; + monitorH = crtc->height; + + // get DPI + float raw = crtc->width / 1920.0f; + float steps[] = {1.0f, 1.2f, 1.5f, 1.75, 2.0f}; + float closest = steps[0]; + float minDiff = fabsf(raw - steps[0]); + + for (int i = 1; i < sizeof(steps) / sizeof(steps[0]); i++) { + float diff = fabsf(raw - steps[i]); + if (diff < minDiff) { + minDiff = diff; + closest = steps[i]; + } + } + + dpi = (uint32_t)(closest * 96.0f); + s_X11.freeCrtcInfo(crtc); + s_X11.freeOutputInfo(outputInfo); + break; + } + s_X11.freeScreenResources(resources); + } + + int32_t x, y = 0; + // the position and size must be scaled with the dpi before this call + if (info->center) { + x = monitorX + (monitorW - info->width) / 2; + y = monitorY + (monitorH - info->height) / 2; + + } else { + // we set 100 for each axix + x = monitorX + 100; + y = monitorY + 100; + } + + // check and set transparency + if (info->style & PAL_WINDOW_STYLE_TRANSPARENT) { + if (!(s_X11.features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW)) { + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + // we dont need to set any flag + } + + long mask = StructureNotifyMask | KeyPressMask; + mask |= KeyReleaseMask; + mask |= ButtonPressMask; + mask |= ButtonReleaseMask; + mask |= PointerMotionMask; + mask |= FocusChangeMask; + mask |= PropertyChangeMask; + + XSetWindowAttributes attrs = {0}; + attrs.colormap = colormap; + attrs.event_mask = mask; + attrs.background_pixel = bgPixel; + attrs.border_pixel = borderPixel; + attrs.override_redirect = False; + + // create window + window = s_X11.createWindow( + s_X11.display, + s_X11.root, + x, + y, + info->width, + info->height, + 0, // border width + depth, + InputOutput, // class + visual, + CWEventMask | CWColormap | CWBorderPixel | CWBackPixel, + &attrs); + + if (window == None) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + // set pid property + pid_t pid = getpid(); + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_PID, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)&pid, + 1); + + // set class property + XClassHint* hints = s_X11.allocClassHint(); + if (hints) { + const char* resName = info->instanceName; + const char* resClass = info->appName; + + if (!resName || strlen(resName) == 0) { + resName = info->title; + } + + if (!resClass || strlen(resClass) == 0) { + resClass = "PAL"; + } + + hints->res_name = (char*)resName; + hints->res_class = (char*)resClass; + s_X11.setClassHint(s_X11.display, window, hints); + s_X11.free(hints); + } + + if (s_X11Atoms.unicodeTitle && info->title) { + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_NAME, + s_X11Atoms.UTF8_STRING, + 8, // unsigned char + PropModeReplace, + (const unsigned char*)info->title, + strlen(info->title)); + + } else { + if (info->title) { + s_X11.storeName(s_X11.display, window, info->title); + } + } + + // borderless + if (info->style & PAL_WINDOW_STYLE_BORDERLESS) { + if (!(s_X11.features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_WINDOW_TYPE, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_SPLASH, + 1); + } + + // tool window + if (info->style & PAL_WINDOW_STYLE_TOOL) { + if (!(s_X11.features & PAL_VIDEO_FEATURE_TOOL_WINDOW)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_WINDOW_TYPE, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*)&s_X11Atoms._NET_WM_WINDOW_TYPE_UTILITY, + 1); + } + + // topmost + if (info->style & PAL_WINDOW_STYLE_TOPMOST) { + if (s_X11Atoms._NET_WM_STATE_ABOVE) { + s_X11.changeProperty( + s_X11.display, + window, + s_X11Atoms._NET_WM_STATE, + XA_ATOM, + 32, + PropModeAppend, + (unsigned char*)&s_X11Atoms._NET_WM_STATE_ABOVE, + 1); + } + } + + // set size hints + XSizeHints wmHints = {0}; + wmHints.flags = PPosition | PSize; + wmHints.x = x; + wmHints.y = y; + wmHints.width = info->width; + wmHints.height = info->height; + + // resizable + if (!(info->style & PAL_WINDOW_STYLE_RESIZABLE)) { + wmHints.flags |= PMinSize; + wmHints.flags |= PMaxSize; + wmHints.min_width = wmHints.max_width = info->width; + wmHints.min_height = wmHints.max_height = info->height; + } + + // show window + s_X11.setWMNormalHints(s_X11.display, window, &wmHints); + if (info->show) { + s_X11.mapWindow(s_X11.display, window); + s_X11.flush(s_X11.display); + } + + // check if the window has been mapped + // we use this to minimize or maximize + XWindowAttributes attr; + s_X11.getWindowAttributes(s_X11.display, window, &attr); + PalBool windowMapped = attr.map_state = IsViewable; + + // maximize + if (info->state == PAL_WINDOW_STATE_MAXIMIZED) { + if (!(s_X11.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + // if the window is not mapped, we wait till its mapped + if (!windowMapped) { + // wait till the window is mapped + for (;;) { + XEvent event; + s_X11.nextEvent(s_X11.display, &event); + if (event.type == MapNotify && event.xmap.window == window) { + windowMapped = PAL_TRUE; + break; + } + } + } + + sendWMEvent( + window, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + PAL_TRUE); // _NET_WM_STATE_ADD + } + + // minimize + if (info->state == PAL_WINDOW_STATE_MINIMIZED) { + if (!(s_X11.features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE)) { + s_X11.destroyWindow(s_X11.display, window); + return PAL_RESULT_CODE_FEATURE_NOT_SUPPORTED; + } + + // if the window is not mapped, we wait till its mapped + if (!windowMapped) { + // wait till the window is mapped + for (;;) { + XEvent event; + s_X11.nextEvent(s_X11.display, &event); + if (event.type == MapNotify && event.xmap.window == window) { + windowMapped = PAL_TRUE; + break; + } + } + } + s_X11.iconifyWindow(s_X11.display, window, s_X11.screen); + } + + s_X11.setWMProtocols(s_X11.display, window, &s_X11Atoms.WM_DELETE_WINDOW, 1); + s_X11.flush(s_X11.display); + + // attach the window data to the window + data->skipConfigure = PAL_TRUE; + data->skipState = PAL_TRUE; + data->isAttached = PAL_FALSE; // PAL_TRUE for attached windows + data->dpi = dpi; // the current window monitor + data->window = TO_PAL_HANDLE(PalWindow, window); + s_X11.saveContext(s_X11.display, window, s_X11.dataID, (XPointer)data); + + // create an input context + data->ic = s_X11.createIC( + s_X11.im, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, + window, + XNFocusWindow, + window, + nullptr); + + if (!data->ic) { + return PAL_RESULT_CODE_PLATFORM_FAILURE; + } + + *outWindow = TO_PAL_HANDLE(PalWindow, window); + return PAL_RESULT_SUCCESS; +} + +void xDestroyWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + + // PAL does not destroy an attached window + if (data->isAttached) { + return; + } + + s_X11.destroyIC(data->ic); + s_X11.destroyWindow(s_X11.display, xWin); + + if (data->colormap != None) { + s_X11.freeColormap(s_X11.display, data->colormap); + } + + data->used = PAL_FALSE; +} + +void xMinimizeWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.iconifyWindow(s_X11.display, xWin, s_X11.screen); +} + +void xMaximizeWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + sendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + PAL_TRUE); // _NET_WM_STATE_ADD +} + +void xRestoreWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + // since we have no fixed way to restore the window + // we just restore from minimized and maximized state + sendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT, + s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ, + 1, + 0, + PAL_FALSE); // _NET_WM_STATE_REMOVE + + s_X11.mapRaised(s_X11.display, xWin); +} + +void xShowWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.mapWindow(s_X11.display, xWin); +} + +void xHideWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.unmapWindow(s_X11.display, xWin); +} + +void xFlashWindow( + PalWindow* window, + const PalFlashInfo* info) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + PalBool add = PAL_FALSE; + if (info->flags & PAL_FLASH_FLAG_TRAY) { + add = PAL_TRUE; + } + + // check if modern flashing is supported + if (s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS) { + sendWMEvent( + xWin, + s_X11Atoms._NET_WM_STATE, + s_X11Atoms._NET_WM_STATE_DEMANDS_ATTENTIONS, + 0, + 0, + 0, + add); // _NET_WM_STATE_ADD + + } else { + // legacy mode + XWMHints* hints = s_X11.getWMHints(s_X11.display, xWin); + if (!hints) { + hints = s_X11.allocWMHints(); + if (!hints) { + return; + } + + if (add) { + hints->flags |= XUrgencyHint; + } else { + hints->flags &= ~XUrgencyHint; + } + s_X11.setWMHints(s_X11.display, xWin, hints); + s_X11.free(hints); + } + } +} + +void xGetWindowTitle( + PalWindow* window, + uint64_t bufferSize, + uint64_t* outSize, + char* outBuffer) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + if (s_X11Atoms.unicodeTitle) { + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* prop = nullptr; + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_NAME, + 0, + (~0L), + False, + s_X11Atoms.UTF8_STRING, + &type, + &format, + &count, + &bytesAfter, + &prop); + + if (bufferSize >= count) { + strcpy(outBuffer, (const char*)prop); + } else { + // copy up to the limiit of the supplied buffer + strncpy(outBuffer, (const char*)prop, bufferSize); + } + s_X11.free(prop); + + } else { + XTextProperty text; + if (!s_X11.getWMName(s_X11.display, xWin, &text)) { + return; + } + + if (bufferSize >= text.nitems) { + strcpy(outBuffer, (const char*)text.value); + } else { + // copy up to the limiit of the supplied buffer + strncpy(outBuffer, (const char*)text.value, bufferSize); + } + s_X11.free(text.value); + } +} + +void xGetWindowPos( + PalWindow* window, + int32_t* x, + int32_t* y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return; + } + + if (x) { + *x = attr.x; + } + + if (y) { + *y = attr.y; + } +} + +void xGetWindowSize( + PalWindow* window, + uint32_t* width, + uint32_t* height) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return; + } + + if (width) { + *width = attr.width; + } + + if (height) { + *height = attr.height; + } +} + +void xGetWindowState( + PalWindow* window, + PalWindowState* outState) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + Atom type; + int format; + unsigned long count, bytesAfter; + unsigned char* props = nullptr; + s_X11.getWindowProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_STATE, + 0, + (~0L), + False, + XA_ATOM, + &type, + &format, + &count, + &bytesAfter, + &props); + + PalWindowState state = PAL_WINDOW_STATE_RESTORED; + for (unsigned long i = 0; i < count; ++i) { + if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_HORZ) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (props[i] == s_X11Atoms._NET_WM_STATE_MAXIMIZED_VERT) { + state = PAL_WINDOW_STATE_MAXIMIZED; + } + + if (props[i] == s_X11Atoms._NET_WM_STATE_HIDDEN) { + state = PAL_WINDOW_STATE_MINIMIZED; + } + } + + s_X11.free(props); + *outState = state; +} + +PalBool xIsWindowVisible(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_FALSE; + } + + return attr.map_state == IsViewable; +} + +PalWindow* xGetFocusWindow() +{ + Window window; + int tmp; + s_X11.getInputFocus(s_X11.display, &window, &tmp); + Window xWin = FROM_PAL_HANDLE(Window, window); + if (xWin == s_X11.root) { + return nullptr; + } + return TO_PAL_HANDLE(PalWindow, window); +} + +void xGetWindowHandleInfo( + PalWindow* window, + PalWindowHandleInfo* info) +{ + info->nativeInstance = (void*)s_X11.display; + info->nativeWindow = (void*)window; + info->nativeHandle1 = nullptr; + info->nativeHandle2 = nullptr; + info->nativeHandle3 = nullptr; +} + +void xSetWindowOpacity( + PalWindow* window, + float opacity) +{ + uint32_t value = (uint32_t)(opacity * (float)0xFFFFFFFFUL + 0.5f); + s_X11.changeProperty( + s_X11.display, + FROM_PAL_HANDLE(Window, window), + s_X11Atoms._NET_WM_WINDOW_OPACITY, + XA_CARDINAL, + 32, + PropModeReplace, + (unsigned char*)&value, + 1); + + s_X11.flush(s_X11.display); +} + +void xSetWindowTitle( + PalWindow* window, + const char* title) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + if (s_X11Atoms.unicodeTitle) { + s_X11.changeProperty( + s_X11.display, + xWin, + s_X11Atoms._NET_WM_NAME, + s_X11Atoms.UTF8_STRING, + 8, // unsigned char + PropModeReplace, + (const unsigned char*)title, + strlen(title)); + + } else { + s_X11.storeName(s_X11.display, xWin, title); + } + + s_X11.flush(s_X11.display); +} + +void xSetWindowPos( + PalWindow* window, + int32_t x, + int32_t y) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + s_X11.moveWindow(s_X11.display, xWin, x, y); + s_X11.flush(s_X11.display); +} + +void xSetWindowSize( + PalWindow* window, + uint32_t width, + uint32_t height) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + // X11 does not allow users resize programaticaly + // if the window is not resizable. + // so we hack it by making the window resizable and resizing + // then revert back. + XSizeHints hints; + long tmp; + s_X11.getWMNormalHints(s_X11.display, xWin, &hints, &tmp); + if ((hints.flags & PMinSize) && (hints.flags & PMaxSize)) { + hints.flags &= ~PMinSize; + hints.flags &= ~PMaxSize; + s_X11.resizeWindow(s_X11.display, xWin, width, height); + s_X11.flush(s_X11.display); + + // revert + hints.flags |= PMinSize; + hints.flags |= PMaxSize; + hints.min_width = hints.max_width = width; + hints.min_height = hints.max_height = height; + + } else { + // window is already resizable + s_X11.resizeWindow(s_X11.display, xWin, width, height); + } + + s_X11.flush(s_X11.display); +} + +void xSetFocusWindow(PalWindow* window) +{ + Window xWin = FROM_PAL_HANDLE(Window, window); + if (s_X11Atoms._NET_ACTIVE_WINDOW) { + sendWMEvent(xWin, s_X11Atoms._NET_ACTIVE_WINDOW, CurrentTime, 0, 0, 0, PAL_TRUE); // 1 + } else { + s_X11.setInputFocus(s_X11.display, xWin, RevertToParent, CurrentTime); + } +} + +PalResult xAttachWindow( + void* windowHandle, + PalWindow** outWindow) +{ + Window xWin = FROM_PAL_HANDLE(Window, windowHandle); + XWindowAttributes attr; + if (!s_X11.getWindowAttributes(s_X11.display, xWin, &attr)) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // get a free slot and set the window handle to it + // we also set a flag to make sure we know this is an attached window + WindowData* data = xGetFreeWindowData(); + if (!data) { + return PAL_RESULT_CODE_OUT_OF_MEMORY; + } + + PalWindow* window = TO_PAL_HANDLE(PalWindow, xWin); + // we assume the window was just created, since there is + // no official way to get the DPI + data->isAttached = PAL_TRUE; + data->dpi = 96; // if this is not the DPI, a dpi event will be triggered + + // If the window manager has not mapped the window yet, + // we dont need the initial Size / Move events + data->skipConfigure = PAL_TRUE; + data->skipState = PAL_TRUE; + data->skipIfAttached = PAL_TRUE; + data->window = window; + data->w = attr.width; + data->h = attr.height; + data->x = attr.x; + data->y = attr.y; + + // get the current window state + // we dont check the return code because we know the window is valid + xGetWindowState(window, &data->state); + + // listen to the events we support + long mask = StructureNotifyMask | KeyPressMask; + mask |= KeyReleaseMask; + mask |= ButtonPressMask; + mask |= ButtonReleaseMask; + mask |= PointerMotionMask; + mask |= FocusChangeMask; + mask |= PropertyChangeMask; + s_X11.selectInput(s_X11.display, xWin, mask); + + // listen to window close event + s_X11.setWMProtocols(s_X11.display, xWin, &s_X11Atoms.WM_DELETE_WINDOW, 1); + + s_X11.saveContext(s_X11.display, xWin, s_X11.dataID, (XPointer)data); + s_X11.flush(s_X11.display); + + *outWindow = window; + return PAL_RESULT_SUCCESS; +} + +PalResult xDetachWindow( + PalWindow* window, + void** outWindowHandle) +{ + // we check is the window is really detachable + Window xWin = FROM_PAL_HANDLE(Window, window); + WindowData* data = nullptr; + s_X11.findContext(s_X11.display, xWin, s_X11.dataID, (XPointer*)&data); + if (!data) { + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + if (data->isAttached == PAL_FALSE) { + // window was created by PAL + return PAL_RESULT_CODE_INVALID_HANDLE; + } + + // detach the window + data->used = PAL_FALSE; + long mask = 0; + s_X11.selectInput(s_X11.display, xWin, mask); + s_X11.setWMProtocols(s_X11.display, xWin, nullptr, 0); + + if (outWindowHandle) { + *outWindowHandle = (void*)window; + } + + return PAL_RESULT_SUCCESS; +} + +#endif // PAL_HAS_X11_BACKEND \ No newline at end of file diff --git a/src/video/x11/pal_x11.h b/src/video/x11/pal_x11.h new file mode 100644 index 00000000..b1ba5c19 --- /dev/null +++ b/src/video/x11/pal_x11.h @@ -0,0 +1,613 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _PAL_X11_H +#define _PAL_X11_H + +#if PAL_HAS_X11_BACKEND == 1 + +#define TO_PAL_HANDLE(type, val) ((type*)(uintptr_t)(val)) +#define FROM_PAL_HANDLE(type, handle) ((type)(uintptr_t)(handle)) + +#include "pal2/pal_video.h" +#include "video/pal_video_egl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define X_INTERN(x) s_X11Atoms.x = s_X11.internAtom(s_X11.display, #x, False) +#define RANDR_SCREEN_CHANGE_EVENT 1040 + +// optionally, needed to create visual from FBConfig +#define GLX_FBCONFIG_ID 0x8012 +typedef struct __GLXFBConfigRec* GLXFBConfig; + +typedef GLXFBConfig* (*GLXGetFBConfigsFn)( + Display*, + int, + int*); + +typedef int (*GLXGetFBConfigAttribFn)( + Display*, + GLXFBConfig, + int, + int*); + +typedef XVisualInfo* (*GLXGetVisualFromFBConfigFn)( + Display*, + GLXFBConfig); + +typedef void (*GLXProc)(); + +typedef GLXProc (*GLXGetProcAddressFn)(const unsigned char*); + +typedef Display* (*XOpenDisplayFn)(const char*); +typedef int (*XCloseDisplayFn)(Display*); + +typedef int (*XGetWindowAttributesFn)( + Display*, + Window, + XWindowAttributes*); + +typedef int (*XGetWindowPropertyFn)( + Display*, + Window, + Atom, + long, + long, + Bool, + Atom, + Atom*, + int*, + unsigned long*, + unsigned long*, + unsigned char**); + +typedef Atom (*XInternAtomFn)( + Display*, + _Xconst char*, + Bool); + +typedef Window (*XGetSelectionOwnerFn)( + Display*, + Atom); + +typedef Window (*XCreateWindowFn)( + Display*, + Window, + int, + int, + unsigned int, + unsigned int, + unsigned int, + int, + unsigned int, + Visual*, + unsigned long, + XSetWindowAttributes*); + +typedef int (*XChangePropertyFn)( + Display*, + Window, + Atom, + Atom, + int, + int, + _Xconst unsigned char*, + int); + +typedef int (*XFlushFn)(Display*); + +typedef Colormap (*XCreateColormapFn)( + Display*, + Window, + Visual*, + int); + +typedef int (*XFreeColormapFn)( + Display*, + Colormap); + +typedef int (*XDestroyWindowFn)( + Display*, + Window); + +typedef int (*XStoreNameFn)( + Display*, + Window, + _Xconst char*); + +typedef int (*XMapWindowFn)( + Display*, + Window); + +typedef int (*XUnmapWindowFn)( + Display*, + Window); + +typedef int (*XMatchVisualInfoFn)( + Display*, + int, + int, + int, + XVisualInfo*); + +typedef int (*XPendingFn)(Display*); + +typedef int (*XSetWMProtocolsFn)( + Display*, + Window, + Atom*, + int); + +typedef int (*XNextEventFn)( + Display*, + XEvent*); + +typedef int (*XSetWMNormalHintsFn)( + Display*, + Window, + XSizeHints*); + +typedef int (*XGetWMNormalHintsFn)( + Display*, + Window, + XSizeHints*, + long*); + +typedef int (*XSendEventFn)( + Display*, + Window, + Bool, + long, + XEvent*); + +typedef int (*XMoveWindowFn)( + Display*, + Window, + int, + int); + +typedef int (*XResizeWindowFn)( + Display*, + Window, + unsigned int, + unsigned int); + +typedef int (*XIconifyWindowFn)( + Display*, + Window, + int); + +typedef XErrorHandler (*XSetErrorHandlerFn)(XErrorHandler); + +typedef int (*XSyncFn)( + Display*, + Bool); + +typedef int (*XSaveContextFn)( + Display*, + XID, + XContext, + _Xconst char*); + +typedef int (*XFindContextFn)( + Display*, + XID, + XContext, + XPointer*); + +typedef XrmQuark (*XrmUniqueQuarkFn)(void); + +typedef int (*XRRSetCrtcConfigFn)( + Display*, + XRRScreenResources*, + RRCrtc, + Time, + int, + int, + RRMode, + Rotation, + RROutput*, + int); + +typedef XRRScreenResources* (*XRRGetScreenResourcesFn)( + Display*, + Window); + +typedef RROutput (*XRRGetOutputPrimaryFn)( + Display*, + Window); + +typedef XRROutputInfo* (*XRRGetOutputInfoFn)( + Display*, + XRRScreenResources*, + RROutput); + +typedef XRRCrtcInfo* (*XRRGetCrtcInfoFn)( + Display*, + XRRScreenResources*, + RRCrtc); + +typedef void (*XRRFreeScreenResourcesFn)(XRRScreenResources*); + +typedef void (*XRRFreeOutputInfoFn)(XRROutputInfo*); + +typedef void (*XRRFreeCrtcInfoFn)(XRRCrtcInfo*); + +typedef void (*XRRSelectInputFn)( + Display*, + Window, + int); + +typedef int (*XRRQueryExtensionFn)( + Display*, + int*, + int*); + +typedef XClassHint* (*XAllocClassHintFn)(void); + +typedef int (*XSetClassHintFn)( + Display*, + Window, + XClassHint*); + +typedef int (*XFreeFn)(void*); + +typedef Cursor (*XCreateFontCursorFn)( + Display*, + unsigned int); + +typedef int (*XFreePixmapFn)( + Display*, + Pixmap); + +typedef int (*XSetWMHintsFn)( + Display*, + Window, + XWMHints*); + +typedef int (*XGrabPointerFn)( + Display*, + Window, + Bool, + unsigned int, + int, + int, + Window, + Cursor, + Time); + +typedef Cursor (*XCreatePixmapCursorFn)( + Display*, + Pixmap, + Pixmap, + XColor*, + XColor*, + unsigned int, + unsigned int); + +typedef int (*XWarpPointerFn)( + Display*, + Window, + Window, + int, + int, + unsigned int, + unsigned int, + int, + int); + +typedef Status (*XGetWMNameFn)( + Display*, + Window, + XTextProperty*); + +typedef Bool (*XQueryPointerFn)( + Display*, + Window, + Window*, + Window*, + int*, + int*, + int*, + int*, + unsigned int*); + +typedef int (*XUngrabPointerFn)( + Display*, + Time); + +typedef XWMHints* (*XAllocWMHintsFn)(void); + +typedef int (*XMapRaisedFn)( + Display*, + Window); + +typedef int (*XUndefineCursorFn)( + Display*, + Window); + +typedef int (*XDefineCursorFn)( + Display*, + Window, + Cursor); + +typedef int (*XFreeCursorFn)( + Display*, + Cursor); + +typedef XWMHints* (*XGetWMHintsFn)( + Display*, + Window); + +typedef int (*XSetInputFocusFn)( + Display*, + Window, + int, + Time); + +typedef int (*XGetInputFocusFn)( + Display*, + Window*, + int*); + +typedef Pixmap (*XCreatePixmapFn)( + Display*, + Drawable, + unsigned int, + unsigned int, + unsigned int); + +typedef XVisualInfo* (*XGetVisualInfoFn)( + Display*, + long, + XVisualInfo*, + int*); + +typedef int (*XSelectInputFn)( + Display*, + Window, + long); + +typedef Cursor (*XcursorImageLoadCursorFn)( + Display*, + const XcursorImage*); + +typedef XcursorImage* (*XcursorImageCreateFn)( + int, + int); + +typedef void (*XcursorImageDestroyFn)(XcursorImage*); + +typedef KeySym (*XLookupKeysymFn)( + XKeyEvent*, + int); + +typedef int (*XkbSetDetectableAutoRepeatFn)( + Display*, + int, + int*); + +typedef char* (*XSetLocaleModifiersFn)(const char*); + +typedef XIM (*XOpenIMFn)( + Display*, + struct _XrmHashBucketRec*, + char*, + char*); + +typedef int (*XCloseIMFn)(XIM); + +typedef XIC (*XCreateICFn)( + XIM, + ...); + +typedef void (*XDestroyICFn)(XIC); + +typedef int (*Xutf8LookupStringFn)( + XIC, + XKeyPressedEvent*, + char*, + int, + KeySym*, + int*); + +typedef struct { + PalBool unicodeTitle; + + Atom WM_DELETE_WINDOW; + Atom _NET_SUPPORTED; + Atom _NET_WM_STATE; + Atom _NET_WM_STATE_ABOVE; + Atom _NET_WM_STATE_HIDDEN; + Atom _NET_WM_STATE_MAXIMIZED_VERT; + Atom _NET_WM_STATE_MAXIMIZED_HORZ; + Atom _NET_WM_WINDOW_TYPE_UTILITY; + Atom _NET_WM_DESKTOP; + Atom _NET_WM_STATE_DEMANDS_ATTENTIONS; + Atom _NET_WM_WINDOW_OPACITY; + + Atom _NET_WM_NAME; + Atom UTF8_STRING; + Atom _NET_WM_WINDOW_TYPE; + Atom _NET_WM_WINDOW_TYPE_SPLASH; + Atom _NET_WM_PID; + Atom _WM_CLASS; + Atom _NET_ACTIVE_WINDOW; + Atom _NET_WM_ICON; +} X11Atoms; + +typedef struct { + PalBool skipConfigure; + PalBool skipState; + PalBool used; + PalBool isAttached; + PalBool skipIfAttached; + int x; + int y; + uint32_t w; + int dpi; + uint32_t h; + PalWindowState state; + PalWindow* window; + void* ic; + unsigned long colormap; +} WindowData; + +typedef struct { + PalBool used; + int dpi; + int x; + int y; + uint32_t w; + uint32_t h; + PalMonitor* monitor; +} MonitorData; + +typedef struct { + PalBool error; + PalBool skipScreenEvent; + int bpp; + int screen; + int depth; + int monitorCount; + int rrEventBase; + void* handle; + void* xrandr; + void* glxHandle; + void* libCursor; + XIM im; + Display* display; + Window root; + XContext dataID; + + int32_t maxWindowData; + int32_t maxMonitorData; + PalVideoFeatures features; + const PalAllocator* allocator; + PalEventDriver* eventDriver; + WindowData* windowData; + MonitorData* monitorData; + + XOpenDisplayFn openDisplay; + XCloseDisplayFn closeDisplay; + XGetWindowAttributesFn getWindowAttributes; + XGetWindowPropertyFn getWindowProperty; + XInternAtomFn internAtom; + XGetSelectionOwnerFn getSelectionOwner; + XFreeColormapFn freeColormap; + XStoreNameFn storeName; + XChangePropertyFn changeProperty; + XFlushFn flush; + XCreateColormapFn createColormap; + XMapWindowFn mapWindow; + XUnmapWindowFn unmapWindow; + + XCreateWindowFn createWindow; + XDestroyWindowFn destroyWindow; + XMatchVisualInfoFn matchVisualInfo; + XPendingFn pending; + XSetWMProtocolsFn setWMProtocols; + XNextEventFn nextEvent; + XSetWMNormalHintsFn setWMNormalHints; + XGetWMNormalHintsFn getWMNormalHints; + XSendEventFn sendEvent; + XMoveWindowFn moveWindow; + XResizeWindowFn resizeWindow; + XIconifyWindowFn iconifyWindow; + + XSetErrorHandlerFn setErrorHandler; + XSyncFn sync; + XSaveContextFn saveContext; + XFindContextFn findContext; + XrmUniqueQuarkFn uniqueContext; + + XRRSetCrtcConfigFn setCrtcConfig; + XRRGetScreenResourcesFn getScreenResources; + XRRGetOutputPrimaryFn getOutputPrimary; + XRRGetOutputInfoFn getOutputInfo; + XRRGetCrtcInfoFn getCrtcInfo; + XRRFreeScreenResourcesFn freeScreenResources; + XRRFreeOutputInfoFn freeOutputInfo; + XRRFreeCrtcInfoFn freeCrtcInfo; + XRRSelectInputFn selectRRInput; + XRRQueryExtensionFn queryRRExtension; + + XAllocClassHintFn allocClassHint; + XSetClassHintFn setClassHint; + XFreeFn free; + XGetVisualInfoFn getVisualInfo; + + // opengl + GLXGetFBConfigsFn glxGetFBConfigs; + GLXGetFBConfigAttribFn glxGetFBConfigAttrib; + GLXGetVisualFromFBConfigFn glxGetVisualFromFBConfig; + + XCreateFontCursorFn createFontCursor; + XFreePixmapFn freePixmap; + XSetWMHintsFn setWMHints; + XGrabPointerFn grabPointer; + XCreatePixmapCursorFn createPixmapCursor; + XWarpPointerFn warpPointer; + XGetWMNameFn getWMName; + XQueryPointerFn queryPointer; + XUngrabPointerFn ungrabPointer; + XAllocWMHintsFn allocWMHints; + XMapRaisedFn mapRaised; + XUndefineCursorFn undefineCursor; + XDefineCursorFn defineCursor; + XFreeCursorFn freeCursor; + XGetWMHintsFn getWMHints; + XCreatePixmapFn createPixmap; + XSetInputFocusFn setInputFocus; + XGetInputFocusFn getInputFocus; + XcursorImageLoadCursorFn cursorImageLoadCursor; + XcursorImageCreateFn cursorImageCreate; + XcursorImageDestroyFn cursorImageDestroy; + XLookupKeysymFn lookupKeysym; + XSelectInputFn selectInput; + + XkbSetDetectableAutoRepeatFn setDetectableAutoRepeat; + XSetLocaleModifiersFn setLocaleModifiers; + XOpenIMFn openIM; + XCloseIMFn closeIM; + XCreateICFn createIC; + XDestroyICFn destroyIC; + Xutf8LookupStringFn utf8LookupString; +} X11; + +extern X11 s_X11; +extern X11Atoms s_X11Atoms; + +RRMode findMode( + XRRScreenResources* resources, + const PalMonitorMode* mode); + +void sendWMEvent( + Window window, + Atom type, + long a, + long b, + long c, + long d, + PalBool add); + +void xGetPrimaryMonitor(PalMonitor**); +MonitorData* xGetFreeMonitorData(); +MonitorData* xFindMonitorData(PalMonitor* monitor); +void xFreeMonitorData(PalMonitor* monitor); +WindowData* xGetFreeWindowData(); +WindowData* xFindWindowData(PalWindow* window); + +#endif // PAL_HAS_X11_BACKEND +#endif // _PAL_X11_H \ No newline at end of file diff --git a/tests/logger_test.c b/tests/core/logger_test.c similarity index 68% rename from tests/logger_test.c rename to tests/core/logger_test.c index a5dc04e8..e60bd52c 100644 --- a/tests/logger_test.c +++ b/tests/core/logger_test.c @@ -5,9 +5,9 @@ // clang-format off static const char* g_LoggerNames[LOGGER_COUNT] = { - "Logger1", - "Logger2", - "Logger3", + "Logger1", + "Logger2", + "Logger3", "Logger4"}; // clang-format on @@ -25,24 +25,18 @@ static void PAL_CALL onLogger( palLog(nullptr, "%s: %s", name, msg); } -bool loggerTest() +PalBool loggerTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Logger Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalLogger loggers[LOGGER_COUNT]; - for (Int32 i = 0; i < LOGGER_COUNT; i++) { + for (int32_t i = 0; i < LOGGER_COUNT; i++) { loggers[i].callback = onLogger; loggers[i].userData = (void*)g_LoggerNames[i]; } - for (Int32 i = 0; i < LOGGER_COUNT; i++) { + for (int32_t i = 0; i < LOGGER_COUNT; i++) { // push a log message to all loggers palLog(&loggers[i], "This is directed to a logger"); } - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/time_test.c b/tests/core/time_test.c similarity index 54% rename from tests/time_test.c rename to tests/core/time_test.c index 02042bd2..9f355392 100644 --- a/tests/time_test.c +++ b/tests/core/time_test.c @@ -3,25 +3,19 @@ // a simple timer object to hold frequency and start time typedef struct { - Uint64 frequency; - Uint64 startTime; + uint64_t frequency; + uint64_t startTime; } MyTimer; // get the time in seconds static inline double getTime(MyTimer* timer) { - Uint64 now = palGetPerformanceCounter(); + uint64_t now = palGetPerformanceCounter(); return (double)(now - timer->startTime) / (double)timer->frequency; } -bool timeTest() +PalBool timeTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Time Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - // create and set the frequency and start time for time related calculations MyTimer timer; timer.frequency = palGetPerformanceFrequency(); @@ -31,27 +25,19 @@ bool timeTest() double lastTime = getTime(&timer); double totalTime = 0.0; - Int32 frameCount = 0; + int32_t frameCount = 0; // run the loop for 5 seconds while (totalTime < 5.0) { double now = getTime(&timer); totalTime = now - lastTime; - palLog( - nullptr, - "Frame %d, Total Time %f seconds", - frameCount, - totalTime); + palLog(nullptr, "Frame %d, Total Time %f seconds", frameCount, totalTime); frameCount++; } - palLog( - nullptr, - "Loop finished after %f seconds and %d frames", - totalTime, - frameCount); + palLog(nullptr, "Loop finished after %f seconds and %d frames", totalTime, frameCount); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/event_test.c b/tests/event/event_test.c similarity index 70% rename from tests/event_test.c rename to tests/event/event_test.c index 2bd211a0..374963dd 100644 --- a/tests/event_test.c +++ b/tests/event/event_test.c @@ -1,22 +1,22 @@ -#include "pal/pal_event.h" +#include "pal2/pal_event.h" #include "tests.h" #define MAX_ITERATIONS 10 #define MAX_EVENTS 100000 -static Uint32 s_CallbackCounter = 0; -static Uint32 s_PollCounter = 0; +static uint32_t s_CallbackCounter = 0; +static uint32_t s_PollCounter = 0; typedef struct { - Uint64 frequency; - Uint64 startTime; + uint64_t frequency; + uint64_t startTime; } MyTimer; // get the time in seconds static inline double getTime(MyTimer* timer) { - Uint64 now = palGetPerformanceCounter(); + uint64_t now = palGetPerformanceCounter(); return (double)(now - timer->startTime) / (double)timer->frequency; } @@ -28,7 +28,7 @@ static void PAL_CALL onEvent( s_CallbackCounter++; } -static inline void eventDispatchTest(bool poll) +static inline PalBool eventDispatchTest(PalBool poll) { PalResult result; PalEventDriver* driver = nullptr; @@ -37,26 +37,25 @@ static inline void eventDispatchTest(bool poll) result = palCreateEventDriver(&createInfo, &driver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // set dispatch mode - PalDispatchMode mode = PAL_DISPATCH_CALLBACK; + PalDispatchMode mode = PAL_DISPATCH_MODE_CALLBACK; if (poll) { - mode = PAL_DISPATCH_POLL; + mode = PAL_DISPATCH_MODE_POLL; } - for (Uint32 e = 0; e < PAL_EVENT_MAX; e++) { + for (uint32_t e = 0; e < PAL_EVENT_TYPE_COUNT; e++) { palSetEventDispatchMode(driver, e, mode); } - Int32 counter = 0; + int32_t counter = 0; while (counter < MAX_EVENTS) { // push all types of event up to max - for (Int32 i = 0; i < MAX_EVENTS; i++) { - PalEventType type = i % PAL_EVENT_MAX; + for (int32_t i = 0; i < MAX_EVENTS; i++) { + PalEventType type = i % PAL_EVENT_TYPE_COUNT; PalEvent event = {0}; event.type = type; palPushEvent(driver, &event); @@ -74,17 +73,11 @@ static inline void eventDispatchTest(bool poll) } palDestroyEventDriver(driver); + return PAL_TRUE; } -bool eventTest() +PalBool eventTest() { - - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Event Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - MyTimer timer; timer.frequency = palGetPerformanceFrequency(); timer.startTime = palGetPerformanceCounter(); @@ -93,8 +86,11 @@ bool eventTest() // get start time double startTime = getTime(&timer); - for (Int32 i = 0; i < MAX_ITERATIONS; i++) { - eventDispatchTest(false); // callback mode first + for (int32_t i = 0; i < MAX_ITERATIONS; i++) { + PalBool success = eventDispatchTest(PAL_FALSE); // callback mode + if (success == PAL_FALSE) { + return PAL_FALSE; + } } // get end time @@ -113,8 +109,11 @@ bool eventTest() // get start time startTime = getTime(&timer); - for (Int32 i = 0; i < MAX_ITERATIONS; i++) { - eventDispatchTest(true); // poll mode first + for (int32_t i = 0; i < MAX_ITERATIONS; i++) { + PalBool success = eventDispatchTest(PAL_TRUE); // poll mode + if (success == PAL_FALSE) { + return PAL_FALSE; + } } // get end time @@ -135,5 +134,5 @@ bool eventTest() // how many times poll event was called palLog(nullptr, "Poll counter: %d", s_PollCounter); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/user_event_test.c b/tests/event/user_event_test.c similarity index 75% rename from tests/user_event_test.c rename to tests/event/user_event_test.c index 75fb7f6c..856e4d31 100644 --- a/tests/user_event_test.c +++ b/tests/event/user_event_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_event.h" +#include "pal2/pal_event.h" #include "tests.h" #define USER_CLOSE_EVENT_ID 44568 @@ -7,29 +7,23 @@ // a simple timer object to hold frequency and start time typedef struct { - Uint64 frequency; - Uint64 startTime; + uint64_t frequency; + uint64_t startTime; } MyTimer; // get the time in seconds static inline double getTime(MyTimer* timer) { - Uint64 now = palGetPerformanceCounter(); + uint64_t now = palGetPerformanceCounter(); return (double)(now - timer->startTime) / (double)timer->frequency; } -bool userEventTest() +PalBool userEventTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "User Event Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; PalEventDriver* eventDriver = nullptr; PalEventDriverCreateInfo createInfo; - bool running, logged = false; + PalBool running, logged = PAL_FALSE; // fill the event driver create info createInfo.allocator = nullptr; // default allocator @@ -40,12 +34,11 @@ bool userEventTest() // create the event driver result = palCreateEventDriver(&createInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } - palSetEventDispatchMode(eventDriver, PAL_EVENT_USER, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_USER, PAL_DISPATCH_MODE_POLL); // create and set the frequency and start time for time related calculations MyTimer timer; @@ -55,12 +48,12 @@ bool userEventTest() // get the start time normalize by timer.startTime double lastTime = getTime(&timer); - running = true; + running = PAL_TRUE; while (running) { PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_USER: { + case PAL_EVENT_TYPE_USER: { // a user event. USe another switch to check which event // using the user Id switch (event.userId) { @@ -68,14 +61,14 @@ bool userEventTest() // log a message if (!logged) { palLog(nullptr, "5 seconds have passed"); - logged = true; + logged = PAL_TRUE; } break; } case USER_CLOSE_EVENT_ID: { palLog(nullptr, "10 seconds have passed"); - running = false; + running = PAL_FALSE; break; } } @@ -91,7 +84,7 @@ bool userEventTest() if (timePassed > 5.0) { PalEvent event = {0}; event.userId = USER_PRINT_EVENT_ID; - event.type = PAL_EVENT_USER; + event.type = PAL_EVENT_TYPE_USER; palPushEvent(eventDriver, &event); } @@ -99,7 +92,7 @@ bool userEventTest() if (timePassed > 10.0) { PalEvent event = {0}; event.userId = USER_CLOSE_EVENT_ID; - event.type = PAL_EVENT_USER; + event.type = PAL_EVENT_TYPE_USER; palPushEvent(eventDriver, &event); } } @@ -107,5 +100,5 @@ bool userEventTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/graphics/clear_color_test.c b/tests/graphics/clear_color_test.c new file mode 100644 index 00000000..a16dedca --- /dev/null +++ b/tests/graphics/clear_color_test.c @@ -0,0 +1,511 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool clearColorTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Clear Color Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + ; + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + + } else { + break; + } + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures adapterFeatures = palGetAdapterFeatures(adapter); + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + PalBarrierInfo barrierInfo = {0}; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + // palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/graphics/compute_test.c b/tests/graphics/compute_test.c new file mode 100644 index 00000000..733769ed --- /dev/null +++ b/tests/graphics/compute_test.c @@ -0,0 +1,448 @@ + +#include "pal2/pal_graphics.h" +#include "tests.h" + +#define BUFFER_SIZE 400 + +// layout must match shader +typedef struct { + uint32_t width; + uint32_t height; + uint32_t _padding[2]; + float color[4]; +} PushConstant; + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool computeTest() +{ + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalQueue* queue = nullptr; + PalCommandPool* cmdPool = nullptr; + PalCommandBuffer* cmdBuffer; + PalShader* shader = nullptr; + PalBuffer* buffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + + PalDescriptorSetLayout* descriptorSetLayout = nullptr; + PalDescriptorPool* descriptorPool = nullptr; + PalDescriptorSet* descriptorSet = nullptr; + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalFence* fence = nullptr; + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + PalResult result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterFeatures adapterFeatures = 0; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxComputeQueues == 0) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + result = palCreateDevice(adapter, 0, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create a compute command queue + result = palCreateQueue(device, PAL_QUEUE_TYPE_COMPUTE, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer(device, cmdPool, PAL_COMMAND_BUFFER_TYPE_PRIMARY, &cmdBuffer); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + + // create a compute shader + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* source = nullptr; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + source = "graphics/shaders/bin/spirv/compute.spv"; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + source = "graphics/shaders/bin/dxil/compute.dxil"; + } + + // read file + if (!readFile(source, nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(source, bytecode, &bytecodeSize); + + PalShaderEntryInfo computeEntry = {0}; + computeEntry.entryName = "main"; + computeEntry.stage = PAL_SHADER_STAGE_COMPUTE; + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &computeEntry; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shader); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + + // create a storage buffer + uint32_t bufferBytes = BUFFER_SIZE * BUFFER_SIZE * sizeof(float) * 4; // must match shader + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = bufferBytes; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_STORAGE | PAL_BUFFER_USAGE_TRANSFER_SRC; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &buffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_DST; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // create descriptor set layout + PalDescriptorSetLayoutBinding descriptorBinding = {0}; + descriptorBinding.descriptorCount = 1; // not an array + descriptorBinding.descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + PalDescriptorSetLayoutCreateInfo descriptorSetLayoutcreateInfo = {0}; + descriptorSetLayoutcreateInfo.bindingCount = 1; + descriptorSetLayoutcreateInfo.bindings = &descriptorBinding; + + result = + palCreateDescriptorSetLayout(device, &descriptorSetLayoutcreateInfo, &descriptorSetLayout); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor set layout"); + return PAL_FALSE; + } + + // create descriptor pool + PalDescriptorPoolBindingSize storageBufferBindingsize = {0}; + storageBufferBindingsize.bindingCount = 1; + storageBufferBindingsize.descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + PalDescriptorPoolCreateInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.maxDescriptorSets = 1; // only one set + descriptorPoolCreateInfo.bindingSizeCount = 1; // one binding type + descriptorPoolCreateInfo.bindingSizes = &storageBufferBindingsize; + + result = palCreateDescriptorPool(device, &descriptorPoolCreateInfo, &descriptorPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor pool"); + return PAL_FALSE; + } + + // allocate a single descriptor set from the descriptor pool + // using the layout we created above + result = palAllocateDescriptorSet(device, descriptorPool, descriptorSetLayout, &descriptorSet); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate descriptor set"); + return PAL_FALSE; + } + + // write the inital data to the descriptor set since its created empty + PalDescriptorBufferInfo descriptorBufferInfo = {0}; + descriptorBufferInfo.buffer = buffer; + descriptorBufferInfo.offset = 0; + descriptorBufferInfo.size = bufferBytes; + + PalDescriptorSetWriteInfo writeInfo = {0}; + writeInfo.layoutBindingIndex = 0; + writeInfo.bufferInfos = &descriptorBufferInfo; + writeInfo.descriptorSet = descriptorSet; + writeInfo.descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + writeInfo.descriptorCount = 1; + + result = palUpdateDescriptorSet(device, 1, &writeInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + // create pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.descriptorSetLayoutCount = 1; + pipelineLayoutCreateInfo.descriptorSetLayouts = &descriptorSetLayout; + pipelineLayoutCreateInfo.usePushConstant = PAL_TRUE; + pipelineLayoutCreateInfo.pushConstantInfo.offset = 0; + pipelineLayoutCreateInfo.pushConstantInfo.size = sizeof(PushConstant); // must match shader + + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + // create a compute pipeline + PalComputePipelineCreateInfo pipelineCreateInfo = {0}; + pipelineCreateInfo.computeShader = shader; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + + result = palCreateComputePipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + palDestroyShader(shader); + + // create fence + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // record commands + result = palCmdBegin(cmdBuffer, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PushConstant pushConstant = {0}; + pushConstant.width = BUFFER_SIZE; + pushConstant.height = BUFFER_SIZE; + pushConstant.color[0] = 1.0f; + pushConstant.color[1] = 0.0f; + pushConstant.color[2] = 0.0f; + pushConstant.color[3] = 1.0f; + + palCmdBindPipeline(cmdBuffer, pipeline); + palCmdPushConstants(cmdBuffer, 0, sizeof(PushConstant), &pushConstant); + palCmdBindDescriptorSet(cmdBuffer, 0, descriptorSet); + + // we will use a helper function to calculate the number of group count + // we need on each axis. The workGroupSize on each axis must be less or equal + // to maxComputeWorkGroupInvocations from the adapter capabilities struct + PalWorkGroupBuildData buildData = {0}; + buildData.workCount[0] = BUFFER_SIZE; + buildData.workCount[1] = BUFFER_SIZE; + buildData.workCount[2] = 1; // no Z + + buildData.workGroupSize[0] = 16; // must match shader (local_size on glsl) + buildData.workGroupSize[1] = 16; // must match shader (local_size on glsl) + buildData.workGroupSize[2] = 1; // must match shader (local_size on glsl) + + // device limits + buildData.workGroupCount[0] = caps.computeCaps.maxWorkGroupCount[0]; + buildData.workGroupCount[1] = caps.computeCaps.maxWorkGroupCount[1]; + buildData.workGroupCount[2] = caps.computeCaps.maxWorkGroupCount[2]; + + uint32_t workGroupInfoCount = 0; + PalWorkGroupInfo* workGroupInfos = nullptr; + palBuildWorkGroupInfo(&buildData, &workGroupInfoCount, nullptr); + + workGroupInfos = palAllocate(nullptr, sizeof(PalWorkGroupInfo) * workGroupInfoCount, 0); + if (!workGroupInfos) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + palBuildWorkGroupInfo(&buildData, &workGroupInfoCount, workGroupInfos); + + // dispatch with the work group info + for (int i = 0; i < workGroupInfoCount; i++) { + // palDispatchBase is not supported on all platforms so we dont use it for this example + // We cap the buffer size small so we only get a single dispatch + // but you can add fields in yout constants to send the base to the shader driectly + uint32_t groupCountX = workGroupInfos[i].workGroupCount[0]; + uint32_t groupCountY = workGroupInfos[i].workGroupCount[1]; + uint32_t groupCountZ = workGroupInfos[i].workGroupCount[2]; + palCmdDispatch(cmdBuffer, groupCountX, groupCountY, groupCountZ); + } + palFree(nullptr, workGroupInfos); + + // set a barrier so we only read from the buffer after the shader has written to it + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_SHADER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_COMPUTE_SHADER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffer, buffer, &barrierInfo); + + // now we copy from the GPU buffer into the staging buffer + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = bufferBytes; + palCmdCopyBuffer(cmdBuffer, stagingBuffer, buffer, ©Info); + + result = palCmdEnd(cmdBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit the command buffer to the GPU + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffer; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COMPUTE_SHADER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // wait for the fence + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // now our staging buffer has the contents of the GPU buffer + // we map it and copy the contents to a ppm buffer and save it + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, bufferBytes, &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + // write to a ppm output file + FILE* file = fopen("compute_output.ppm", "wb"); + fprintf(file, "P6\n%d %d\n255\n", BUFFER_SIZE, BUFFER_SIZE); + float* pixels = (float*)ptr; + for (int y = 0; y < BUFFER_SIZE; y++) { + int row = BUFFER_SIZE - 1 - y; // flip y + for (int x = 0; x < BUFFER_SIZE; x++) { + int index = row * BUFFER_SIZE + x; + uint8_t rgb[3]; + + rgb[0] = pixels[index * 4 + 0] > 0.5f ? 255 : 0; + rgb[1] = pixels[index * 4 + 1] > 0.5f ? 255 : 0; + rgb[2] = pixels[index * 4 + 2] > 0.5f ? 255 : 0; + fwrite(rgb, 1, 3, file); + } + } + + fclose(file); + palUnmapBuffer(stagingBuffer); + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + palFreeCommandBuffer(cmdBuffer); + palDestroyCommandPool(cmdPool); + + palDestroyDescriptorPool(descriptorPool); + palDestroyDescriptorSetLayout(descriptorSetLayout); + + palDestroyBuffer(buffer); + palDestroyBuffer(stagingBuffer); + + palDestroyFence(fence); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + return PAL_TRUE; +} diff --git a/tests/graphics/custom_backend_test.c b/tests/graphics/custom_backend_test.c new file mode 100644 index 00000000..7715f253 --- /dev/null +++ b/tests/graphics/custom_backend_test.c @@ -0,0 +1,1110 @@ + +#include "pal2/pal_graphics.h" +#include "tests.h" + +// we use the same fields for each of our handles so we just typedef a base handle struct +typedef struct { + void* reserved; +} Adapter; + +// we will only expose a single adapter +static Adapter s_Adapter; +static PalAdapterInfo s_Info; +static PalAdapterCapabilities s_Cap; + +static void initBackend() +{ + s_Adapter.reserved = nullptr; + s_Info.apiType = PAL_ADAPTER_API_TYPE_VULKAN; + strcpy(s_Info.backendName, "Custom"); + s_Info.deviceId = 1666; + s_Info.driverVersion = 1; + strcpy(s_Info.name, "GXR 7060"); + s_Info.shaderFormats = PAL_SHADER_FORMAT_SPIRV | PAL_SHADER_FORMAT_GLSL; + s_Info.sharedMemory = (uint64_t)(1024 * 1024 * 1024) * (uint64_t)2; + s_Info.type = PAL_ADAPTER_TYPE_DISCRETE; + s_Info.vendorId = 20037; + s_Info.vram = (uint64_t)(1024 * 1024 * 1024) * (uint64_t)8; + + s_Cap.computeCaps.maxWorkGroupCount[0] = 65535; + s_Cap.computeCaps.maxWorkGroupCount[1] = 65535; + s_Cap.computeCaps.maxWorkGroupCount[2] = 65535; + s_Cap.computeCaps.maxWorkGroupSize[0] = 1024; + s_Cap.computeCaps.maxWorkGroupSize[1] = 1024; + s_Cap.computeCaps.maxWorkGroupSize[2] = 64; + s_Cap.computeCaps.maxWorkGroupInvocations = 1024; + + s_Cap.imageCaps.maxArrayLayers = 5; + s_Cap.imageCaps.maxDepth = 128; + s_Cap.imageCaps.maxHeight = 4800; + s_Cap.imageCaps.maxMipLevels = 12; + s_Cap.imageCaps.maxWidth = 4800; + + s_Cap.maxColorAttachments = 8; + s_Cap.maxComputeQueues = 2; + s_Cap.maxCopyQueues = 2; + s_Cap.maxGraphicsQueues = 2; + s_Cap.maxPushConstantSize = 128; + + s_Cap.maxStorageBufferSize = 65536; + s_Cap.maxTessellationPatchPoint = 8; + s_Cap.maxUniformBufferSize = 65536; + s_Cap.maxVertexAttributes = 14; + s_Cap.maxVertexLayouts = 4; + + s_Cap.resourceCaps.maxBoundSets = 2; + s_Cap.resourceCaps.maxPerSetAccelerationStructure = 2; + s_Cap.resourceCaps.maxPerSetSampledImages = 512; + s_Cap.resourceCaps.maxPerSetSamplers = 256; + s_Cap.resourceCaps.maxPerSetStorageBuffers = 720; + s_Cap.resourceCaps.maxPerSetStorageImages = 720; + s_Cap.resourceCaps.maxPerSetUniformBuffers = 100; + s_Cap.resourceCaps.maxPerStageAccelerationStructure = 10; + s_Cap.resourceCaps.maxPerStageSampledImages = 120; + s_Cap.resourceCaps.maxPerStageSamplers = 16; + s_Cap.resourceCaps.maxPerStageStorageBuffers = 50; + s_Cap.resourceCaps.maxPerStageStorageImages = 50; + s_Cap.resourceCaps.maxPerStageUniformBuffers = 50; + + s_Cap.viewportCaps.maxBoundsRange = 4800.0f; + s_Cap.viewportCaps.minBoundsRange = 4800.0f; + s_Cap.viewportCaps.maxWidth = 4800; + s_Cap.viewportCaps.maxHeight = 4800; +} + +static PalResult PAL_CALL enumerateAdapters( + uint32_t* count, + PalAdapter** outAdapters) +{ + if (outAdapters) { + outAdapters[0] = (PalAdapter*)&s_Adapter; + + } else { + *count = 1; + } + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL getAdapterInfo( + PalAdapter* adapter, + PalAdapterInfo* info) +{ + *info = s_Info; +} + +static void PAL_CALL getAdapterCapabilities( + PalAdapter* adapter, + PalAdapterCapabilities* caps) +{ + *caps = s_Cap; +} + +static PalAdapterFeatures PAL_CALL getAdapterFeatures(PalAdapter* adapter) +{ + return PAL_ADAPTER_FEATURE_FENCE_RESET | PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY; +} + +static uint32_t PAL_CALL getHighestSupportedShaderTarget( + PalAdapter* adapter, + PalShaderFormats shaderFormat) +{ + return PAL_MAKE_SHADER_TARGET(1, 0); +} + +static PalResult PAL_CALL createDevice( + PalAdapter* adapter, + PalAdapterFeatures features, + PalDevice** outDevice) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyDevice(PalDevice* device) +{ +} + +static uint32_t PAL_CALL getDeviceLostReason(PalDevice* device) +{ + return 1; +} + +static PalResult PAL_CALL allocateMemory( + PalDevice* device, + PalMemoryType type, + uint64_t memoryMask, + uint64_t size, + PalMemory** outMemory) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL freeMemory(PalMemory* memory) +{ +} + +static void PAL_CALL querySamplerAnisotropyCapabilities( + PalDevice* device, + PalSamplerAnisotropyCapabilities* caps) +{ +} + +static PalResult PAL_CALL createQueue( + PalDevice* device, + PalQueueType type, + PalQueue** outQueue) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyQueue(PalQueue* queue) +{ +} + +static PalBool PAL_CALL canQueuePresent( + PalQueue* queue, + PalSurface* surface) +{ + return PAL_FALSE; +} + +static PalResult PAL_CALL waitQueue(PalQueue* queue) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL enumerateFormats( + PalAdapter* adapter, + uint32_t* count, + PalFormatInfo* outFormats) +{ +} + +static PalBool PAL_CALL isFormatSupported( + PalAdapter* adapter, + PalFormat format) +{ + return PAL_FALSE; +} + +static PalImageUsages PAL_CALL queryFormatImageUsages( + PalAdapter* adapter, + PalFormat format) +{ + return 0; +} + +static PalSampleCount PAL_CALL queryFormatSampleCount( + PalAdapter* adapter, + PalFormat format) +{ + return 0; +} + +static PalResult PAL_CALL createImage( + PalDevice* device, + const PalImageCreateInfo* info, + PalImage** outImage) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyImage(PalImage* image) +{ +} + +static void PAL_CALL getImageInfo( + PalImage* image, + PalImageInfo* info) +{ +} + +static void PAL_CALL getImageMemoryRequirements( + PalImage* image, + PalMemoryRequirements* requirements) +{ +} + +static PalResult PAL_CALL bindImageMemory( + PalImage* image, + PalMemory* memory, + uint64_t offset) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL createImageView( + PalDevice* device, + PalImage* image, + const PalImageViewCreateInfo* info, + PalImageView** outImageView) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyImageView(PalImageView* imageView) +{ +} + +static PalResult PAL_CALL createSampler( + PalDevice* device, + const PalSamplerCreateInfo* info, + PalSampler** outSampler) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroySampler(PalSampler* sampler) +{ +} + +static PalResult PAL_CALL createShader( + PalDevice* device, + const PalShaderCreateInfo* info, + PalShader** outShader) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyShader(PalShader* shader) +{ +} + +static PalResult PAL_CALL createFence( + PalDevice* device, + PalBool signaled, + PalFence** outFence) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyFence(PalFence* fence) +{ +} + +static PalResult PAL_CALL waitFence( + PalFence* fence, + uint64_t timeout) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL resetFence(PalFence* fence) +{ + return PAL_RESULT_SUCCESS; +} + +static PalBool PAL_CALL isFenceSignaled(PalFence* fence) +{ + return PAL_FALSE; +} + +static PalResult PAL_CALL createSemaphore( + PalDevice* device, + PalBool enableTimeline, + PalSemaphore** outSemaphore) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroySemaphore(PalSemaphore* semaphore) +{ +} + +static PalResult PAL_CALL createCommandPool( + PalDevice* device, + PalQueue* queue, + PalCommandPool** outPool) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyCommandPool(PalCommandPool* pool) +{ +} + +static PalResult PAL_CALL allocateCommandBuffer( + PalDevice* device, + PalCommandPool* pool, + PalCommandBufferType type, + PalCommandBuffer** outCmdBuffer) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL freeCommandBuffer(PalCommandBuffer* cmdBuffer) +{ +} + +static PalResult PAL_CALL resetCommandBuffer(PalCommandBuffer* cmdBuffer) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL submitCommandBuffer( + PalQueue* queue, + PalCommandBufferSubmitInfo* info) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL cmdBegin( + PalCommandBuffer* cmdBuffer, + PalRenderingLayoutInfo* info) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL cmdEnd(PalCommandBuffer* cmdBuffer) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL cmdExecuteCommandBuffer( + PalCommandBuffer* primaryCmdBuffer, + PalCommandBuffer* secondaryCmdBuffer) +{ +} + +static void PAL_CALL cmdSetFragmentShadingRate( + PalCommandBuffer* cmdBuffer, + PalFragmentShadingRateState* state) +{ +} + +static void PAL_CALL cmdDrawMeshTasks( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ +} + +static void PAL_CALL cmdDrawMeshTasksIndirect( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint32_t drawCount) +{ +} + +static void PAL_CALL cmdDrawMeshTasksIndirectCount( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBuffer* countBuffer, + uint32_t maxDrawCount) +{ +} + +static void PAL_CALL cmdBuildAccelerationStructure( + PalCommandBuffer* cmdBuffer, + PalAccelerationStructureBuildInfo* info) +{ +} + +static void PAL_CALL cmdBeginRendering( + PalCommandBuffer* cmdBuffer, + PalRenderingInfo* info) +{ +} + +static void PAL_CALL cmdEndRendering(PalCommandBuffer* cmdBuffer) +{ +} + +static void PAL_CALL cmdCopyBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dst, + PalBuffer* src, + PalBufferCopyInfo* copyInfo) +{ +} + +static void PAL_CALL cmdCopyBufferToImage( + PalCommandBuffer* cmdBuffer, + PalImage* dstImage, + PalBuffer* srcBuffer, + PalBufferImageCopyInfo* copyInfo) +{ +} + +static void PAL_CALL cmdCopyImage( + PalCommandBuffer* cmdBuffer, + PalImage* dst, + PalImage* src, + PalImageCopyInfo* copyInfo) +{ +} + +static void PAL_CALL cmdCopyImageToBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* dstBuffer, + PalImage* srcImage, + PalBufferImageCopyInfo* copyInfo) +{ +} + +static void PAL_CALL cmdBindPipeline( + PalCommandBuffer* cmdBuffer, + PalPipeline* pipeline) +{ +} + +static void PAL_CALL cmdSetViewport( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalViewport* viewports) +{ +} + +static void PAL_CALL cmdSetScissors( + PalCommandBuffer* cmdBuffer, + uint32_t count, + PalRect2D* scissors) +{ +} + +static void PAL_CALL cmdBindVertexBuffers( + PalCommandBuffer* cmdBuffer, + uint32_t firstSlot, + uint32_t count, + PalBuffer** buffers, + uint64_t* offsets) +{ +} + +static void PAL_CALL cmdBindIndexBuffer( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + uint64_t offset, + PalIndexType type) +{ +} + +static void PAL_CALL cmdDraw( + PalCommandBuffer* cmdBuffer, + uint32_t vertexCount, + uint32_t instanceCount, + uint32_t firstVertex, + uint32_t firstInstance) +{ +} + +static void PAL_CALL cmdDrawIndexed( + PalCommandBuffer* cmdBuffer, + uint32_t indexCount, + uint32_t instanceCount, + uint32_t firstIndex, + int32_t vertexOffset, + uint32_t firstInstance) +{ +} + +static void PAL_CALL cmdImageBarrier( + PalCommandBuffer* cmdBuffer, + PalImage* image, + PalImageSubresourceRange* subresourceRange, + PalBarrierInfo* info) +{ +} + +static void PAL_CALL cmdBufferBarrier( + PalCommandBuffer* cmdBuffer, + PalBuffer* buffer, + PalBarrierInfo* info) +{ +} + +static void PAL_CALL cmdDispatch( + PalCommandBuffer* cmdBuffer, + uint32_t groupCountX, + uint32_t groupCountY, + uint32_t groupCountZ) +{ +} + +static void PAL_CALL cmdBindDescriptorSet( + PalCommandBuffer* cmdBuffer, + uint32_t setIndex, + PalDescriptorSet* set) +{ +} + +static void PAL_CALL cmdPushConstants( + PalCommandBuffer* cmdBuffer, + uint32_t offset, + uint32_t size, + const void* value) +{ +} + +static PalResult PAL_CALL createBuffer( + PalDevice* device, + const PalBufferCreateInfo* info, + PalBuffer** outBuffer) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyBuffer(PalBuffer* buffer) +{ +} + +static void PAL_CALL getBufferMemoryRequirements( + PalBuffer* buffer, + PalMemoryRequirements* requirements) +{ +} + +static void PAL_CALL computeImageStagingRequirements( + PalDevice* device, + PalFormat imageFormat, + const PalBufferImageCopyInfo* copyInfo, + PalImageStagingRequirements* requirements) +{ +} + +static void PAL_CALL writeImageStaging( + PalDevice* device, + PalFormat imageFormat, + PalBufferImageCopyInfo* copyInfo, + void* srcData, + void* ptr) +{ +} + +static PalResult PAL_CALL bindBufferMemory( + PalBuffer* buffer, + PalMemory* memory, + uint64_t offset) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL mapBuffer( + PalBuffer* buffer, + uint64_t offset, + uint64_t size, + void** outPtr) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL unmapBuffer(PalBuffer* buffer) +{ +} + +static PalResult PAL_CALL createDescriptorSetLayout( + PalDevice* device, + const PalDescriptorSetLayoutCreateInfo* info, + PalDescriptorSetLayout** outLayout) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyDescriptorSetLayout(PalDescriptorSetLayout* layout) +{ +} + +static PalResult PAL_CALL createDescriptorPool( + PalDevice* device, + const PalDescriptorPoolCreateInfo* info, + PalDescriptorPool** outPool) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyDescriptorPool(PalDescriptorPool* pool) +{ +} + +static PalResult PAL_CALL resetDescriptorPool(PalDescriptorPool* pool) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL allocateDescriptorSet( + PalDevice* device, + PalDescriptorPool* pool, + PalDescriptorSetLayout* layout, + PalDescriptorSet** outSet) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL updateDescriptorSet( + PalDevice* device, + uint32_t count, + PalDescriptorSetWriteInfo* infos) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL createPipelineLayout( + PalDevice* device, + const PalPipelineLayoutCreateInfo* info, + PalPipelineLayout** outLayout) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyPipelineLayout(PalPipelineLayout* layout) +{ +} + +static PalResult PAL_CALL createGraphicsPipeline( + PalDevice* device, + const PalGraphicsPipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + return PAL_RESULT_SUCCESS; +} + +static PalResult PAL_CALL createComputePipeline( + PalDevice* device, + const PalComputePipelineCreateInfo* info, + PalPipeline** outPipeline) +{ + return PAL_RESULT_SUCCESS; +} + +static void PAL_CALL destroyPipeline(PalPipeline* pipeline) +{ +} + +PalBool customBackendTest() +{ + // build the vtable + PalGraphicsBackendVtable1 vtable = {0}; + vtable.enumerateAdapters = enumerateAdapters; + vtable.getAdapterInfo = getAdapterInfo; + vtable.getAdapterCapabilities = getAdapterCapabilities; + vtable.getAdapterFeatures = getAdapterFeatures; + vtable.getHighestSupportedShaderTarget = getHighestSupportedShaderTarget; + vtable.createDevice = createDevice; + vtable.destroyDevice = destroyDevice; + vtable.getDeviceLostReason = getDeviceLostReason; + vtable.allocateMemory = allocateMemory; + vtable.freeMemory = freeMemory; + vtable.querySamplerAnisotropyCapabilities = querySamplerAnisotropyCapabilities; + vtable.createQueue = createQueue; + vtable.destroyQueue = destroyQueue; + vtable.waitQueue = waitQueue; + vtable.canQueuePresent = canQueuePresent; + vtable.enumerateFormats = enumerateFormats; + vtable.isFormatSupported = isFormatSupported; + vtable.queryFormatImageUsages = queryFormatImageUsages; + vtable.queryFormatSampleCount = queryFormatSampleCount; + vtable.createImage = createImage; + vtable.destroyImage = destroyImage; + vtable.getImageInfo = getImageInfo; + vtable.getImageMemoryRequirements = getImageMemoryRequirements; + vtable.bindImageMemory = bindImageMemory; + vtable.createImageView = createImageView; + vtable.destroyImageView = destroyImageView; + vtable.createSampler = createSampler; + vtable.destroySampler = destroySampler; + vtable.createShader = createShader; + vtable.destroyShader = destroyShader; + vtable.createFence = createFence; + vtable.destroyFence = destroyFence; + vtable.waitFence = waitFence; + vtable.resetFence = resetFence; + vtable.isFenceSignaled = isFenceSignaled; + vtable.createSemaphore = createSemaphore; + vtable.destroySemaphore = destroySemaphore; + vtable.createCommandPool = createCommandPool; + vtable.destroyCommandPool = destroyCommandPool; + vtable.allocateCommandBuffer = allocateCommandBuffer; + vtable.freeCommandBuffer = freeCommandBuffer; + vtable.resetCommandBuffer = resetCommandBuffer; + vtable.submitCommandBuffer = submitCommandBuffer; + vtable.cmdBegin = cmdBegin; + vtable.cmdEnd = cmdEnd; + vtable.cmdExecuteCommandBuffer = cmdExecuteCommandBuffer; + vtable.cmdBeginRendering = cmdBeginRendering; + vtable.cmdEndRendering = cmdEndRendering; + vtable.cmdCopyBuffer = cmdCopyBuffer; + vtable.cmdCopyBufferToImage = cmdCopyBufferToImage; + vtable.cmdCopyImage = cmdCopyImage; + vtable.cmdCopyImageToBuffer = cmdCopyImageToBuffer; + vtable.cmdBindPipeline = cmdBindPipeline; + vtable.cmdSetViewport = cmdSetViewport; + vtable.cmdSetScissors = cmdSetScissors; + vtable.cmdBindVertexBuffers = cmdBindVertexBuffers; + vtable.cmdBindIndexBuffer = cmdBindIndexBuffer; + vtable.cmdDraw = cmdDraw; + vtable.cmdDrawIndexed = cmdDrawIndexed; + vtable.cmdImageBarrier = cmdImageBarrier; + vtable.cmdBufferBarrier = cmdBufferBarrier; + vtable.cmdDispatch = cmdDispatch; + vtable.cmdBindDescriptorSet = cmdBindDescriptorSet; + vtable.cmdPushConstants = cmdPushConstants; + vtable.createBuffer = createBuffer; + vtable.destroyBuffer = destroyBuffer; + vtable.getBufferMemoryRequirements = getBufferMemoryRequirements; + vtable.computeImageStagingRequirements = computeImageStagingRequirements; + vtable.writeImageStaging = writeImageStaging; + vtable.bindBufferMemory = bindBufferMemory; + vtable.mapBuffer = mapBuffer; + vtable.unmapBuffer = unmapBuffer; + vtable.createDescriptorSetLayout = createDescriptorSetLayout; + vtable.destroyDescriptorSetLayout = destroyDescriptorSetLayout; + vtable.createDescriptorPool = createDescriptorPool; + vtable.destroyDescriptorPool = destroyDescriptorPool; + vtable.resetDescriptorPool = resetDescriptorPool; + vtable.allocateDescriptorSet = allocateDescriptorSet; + vtable.updateDescriptorSet = updateDescriptorSet; + vtable.createPipelineLayout = createPipelineLayout; + vtable.destroyPipelineLayout = destroyPipelineLayout; + vtable.createGraphicsPipeline = createGraphicsPipeline; + vtable.createComputePipeline = createComputePipeline; + vtable.destroyPipeline = destroyPipeline; + + PalGraphicsBackendInfo backendInfo = {0}; + backendInfo.version = PAL_GRAPHICS_BACKEND_VTABLE_VERSION_1; + backendInfo.vtable = &vtable; + + // do any backend initialization before graphics init + initBackend(); + + PalResult result = palInitGraphics(nullptr, nullptr, 1, &backendInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t count = 0; + result = palEnumerateAdapters(&count, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get query adapters"); + return PAL_FALSE; + } + + if (count == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %u", count); + + // allocate an array of adapters or use a fixed array + // Example: PalAdapter* adapters[12]; + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * count, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&count, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get query adapters"); + return PAL_FALSE; + } + + for (int32_t i = 0; i < count; i++) { + PalAdapter* adapter = adapters[i]; + + PalAdapterInfo info; + palGetAdapterInfo(adapter, &info); + + PalAdapterCapabilities caps; + palGetAdapterCapabilities(adapter, &caps); + PalAdapterFeatures features = palGetAdapterFeatures(adapter); + + uint64_t vramMib = (uint64_t)info.vram / (1024 * 1024); + uint64_t sharedMemMib = (uint64_t)info.sharedMemory / (1024 * 1024); + + palLog(nullptr, "GPU Name: %s", info.name); + palLog(nullptr, " Backend Name: %s", info.backendName); + palLog(nullptr, " Driver Version: %llu", info.driverVersion); + palLog(nullptr, " Vram %llu MIB", vramMib); + palLog(nullptr, " Shared Memory %llu MIB", sharedMemMib); + palLog(nullptr, " Device Id: %u", info.deviceId); + palLog(nullptr, " Vendor Id: %u", info.vendorId); + + const char* typeString; + switch (info.type) { + case PAL_ADAPTER_TYPE_INTEGRATED: { + typeString = "Integrated"; + break; + } + + case PAL_ADAPTER_TYPE_VIRTUAL: { + typeString = "Virtual"; + break; + } + + case PAL_ADAPTER_TYPE_DISCRETE: { + typeString = "Discrete"; + break; + } + + case PAL_ADAPTER_TYPE_CPU: { + typeString = "CPU"; + break; + } + } + palLog(nullptr, " Type: %s", typeString); + + const char* apiTypeString; + switch (info.apiType) { + case PAL_ADAPTER_API_TYPE_D3D12: { + apiTypeString = "D3D12"; + break; + } + + case PAL_ADAPTER_API_TYPE_VULKAN: { + apiTypeString = "Vulkan"; + break; + } + + case PAL_ADAPTER_API_TYPE_METAL: { + apiTypeString = "Metal"; + break; + } + } + palLog(nullptr, " API Type: %s", apiTypeString); + + palLog(nullptr, ""); + palLog(nullptr, " Capabilities:"); + palLog(nullptr, " Max compute queue: %u", caps.maxComputeQueues); + palLog(nullptr, " Max graphics queue: %u", caps.maxGraphicsQueues); + palLog(nullptr, " Max copy queue: %u", caps.maxCopyQueues); + palLog(nullptr, " Max color attachments: %u", caps.maxColorAttachments); + palLog(nullptr, " Max uniform buffer size: %u Bytes", caps.maxUniformBufferSize); + + palLog(nullptr, " Max storage buffer size: %u Bytes", caps.maxStorageBufferSize); + palLog(nullptr, " Max push constant size: %u Bytes", caps.maxPushConstantSize); + palLog(nullptr, " Max vertex layouts: %u", caps.maxVertexLayouts); + palLog(nullptr, " Max vertex attributes: %u", caps.maxVertexAttributes); + palLog(nullptr, " Max tessellation patch point: %u", caps.maxTessellationPatchPoint); + + palLog(nullptr, ""); + palLog(nullptr, " Viewport Capabilities:"); + palLog(nullptr, " Max width: %u", caps.viewportCaps.maxWidth); + palLog(nullptr, " Max height: %u", caps.viewportCaps.maxHeight); + palLog(nullptr, " Min bounds range: %.1f", caps.viewportCaps.minBoundsRange); + palLog(nullptr, " Max bounds range: %.1f", caps.viewportCaps.maxBoundsRange); + + palLog(nullptr, ""); + palLog(nullptr, " Image Capabilities:"); + palLog(nullptr, " Max width: %u", caps.imageCaps.maxWidth); + palLog(nullptr, " Max height: %u", caps.imageCaps.maxHeight); + palLog(nullptr, " Max depth: %u", caps.imageCaps.maxDepth); + palLog(nullptr, " Max array layers: %u", caps.imageCaps.maxArrayLayers); + palLog(nullptr, " Max mip levels: %u", caps.imageCaps.maxMipLevels); + + palLog(nullptr, ""); + palLog(nullptr, " Resource Capabilities:"); + PalResourceCapabilities* resourceCaps = &caps.resourceCaps; + + // clang-format off + palLog(nullptr, " Max per stage sampled images: %u", resourceCaps->maxPerStageSampledImages); + palLog(nullptr, " Max per set sampled images: %u", resourceCaps->maxPerSetSampledImages); + palLog(nullptr, " Max per stage storage images: %u", resourceCaps->maxPerStageStorageImages); + palLog(nullptr, " Max per set storage images: %u", resourceCaps->maxPerSetStorageImages); + + palLog(nullptr, " Max per stage samplers: %u", resourceCaps->maxPerStageSamplers); + palLog(nullptr, " Max per set samplers: %u", resourceCaps->maxPerSetSamplers); + palLog(nullptr, " Max per stage storage buffers: %u", resourceCaps->maxPerStageStorageBuffers); + palLog(nullptr, " Max per set storage buffers: %u", resourceCaps->maxPerSetStorageBuffers); + + palLog(nullptr, " Max per stage uniform buffers: %u", resourceCaps->maxPerStageUniformBuffers); + palLog(nullptr, " Max per set uniform buffers: %u", resourceCaps->maxPerSetUniformBuffers); + palLog(nullptr, " Max per stage acceleration structures: %u", resourceCaps->maxPerStageAccelerationStructure); + palLog(nullptr, " Max per set acceleration structures: %u", resourceCaps->maxPerSetAccelerationStructure); + palLog(nullptr, " Max bound sets: %u", resourceCaps->maxBoundSets); + // clang-format on + + palLog(nullptr, ""); + palLog(nullptr, " Compute Capabilities:"); + palLog(nullptr, " Max invocations: %u", caps.computeCaps.maxWorkGroupInvocations); + palLog(nullptr, " Max work group count[0]: %u", caps.computeCaps.maxWorkGroupCount[0]); + palLog(nullptr, " Max work group count[1]: %u", caps.computeCaps.maxWorkGroupCount[1]); + palLog(nullptr, " Max work group count[2]: %u", caps.computeCaps.maxWorkGroupCount[2]); + palLog(nullptr, " Max work group size[0]: %u", caps.computeCaps.maxWorkGroupSize[0]); + palLog(nullptr, " Max work group size[1]: %u", caps.computeCaps.maxWorkGroupSize[1]); + palLog(nullptr, " Max work group size[2]: %u", caps.computeCaps.maxWorkGroupSize[2]); + + // shader formats + uint32_t target; + uint32_t targetMajor = 0; + uint32_t targetMinor = 0; + + palLog(nullptr, ""); + palLog(nullptr, " Supported Shader Formats:"); + if (info.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + palLog(nullptr, " SPIRV"); + } + + if (info.shaderFormats & PAL_SHADER_FORMAT_DXBC) { + palLog(nullptr, " DXBC"); + } + + if (info.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + palLog(nullptr, " DXIL"); + } + + if (info.shaderFormats & PAL_SHADER_FORMAT_GLSL) { + palLog(nullptr, " GLSL"); + } + + // features + palLog(nullptr, ""); + palLog(nullptr, " Supported Features:"); + if (features & PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY) { + palLog(nullptr, " Sampler Anisotropy"); + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEWPORT) { + palLog(nullptr, " Multi viewport"); + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + palLog(nullptr, " Ray tracing"); + } + + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + palLog(nullptr, " Mesh and task shader"); + } + + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) { + palLog(nullptr, " Fragment shading rate"); + } + + if (features & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING) { + palLog(nullptr, " Descriptor indexing"); + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEW) { + palLog(nullptr, " Multiview"); + } + + if (features & PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE) { + palLog(nullptr, " Depth stencil resolve"); + } + + if (features & PAL_ADAPTER_FEATURE_SWAPCHAIN) { + palLog(nullptr, " Swapchain"); + } + + if (features & PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING) { + palLog(nullptr, " Sample rate shading"); + } + + if (features & PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE) { + palLog(nullptr, " Timeline Semaphore"); + } + + if (features & PAL_ADAPTER_FEATURE_TESSELLATION_SHADER) { + palLog(nullptr, " Tesselation Shader"); + } + + if (features & PAL_ADAPTER_FEATURE_GEOMETRY_SHADER) { + palLog(nullptr, " Geometry shader"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT16) { + palLog(nullptr, " Shader float16"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT64) { + palLog(nullptr, " Shader float64"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT16) { + palLog(nullptr, " Shader int16"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT64) { + palLog(nullptr, " Shader int64"); + } + + if (features & PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY) { + palLog(nullptr, " Image view type Cube array"); + } + + if (features & PAL_ADAPTER_FEATURE_FENCE_RESET) { + palLog(nullptr, " Resetting fence"); + } + + if (features & PAL_ADAPTER_FEATURE_POLYGON_MODE_LINE) { + palLog(nullptr, " Polygon mode line"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE) { + palLog(nullptr, " Dynamic cull mode"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE) { + palLog(nullptr, " Dynamic front face"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY) { + palLog(nullptr, " Dynamic primitive topology"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE) { + palLog(nullptr, " Dynamic depth test enable"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE) { + palLog(nullptr, " Dynamic depth write enable"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP) { + palLog(nullptr, " Dynamic stencil op"); + } + + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT) { + palLog(nullptr, " Fragment shading rate attachment"); + } + + if (features & PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS) { + palLog(nullptr, " Buffer device address"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW) { + palLog(nullptr, " Indirect draw"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT) { + palLog(nullptr, " Indirect draw count"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH) { + palLog(nullptr, " Indirect mesh draw"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT) { + palLog(nullptr, " Indirect mesh draw count"); + } + + if (features & PAL_ADAPTER_FEATURE_DISPATCH_BASE) { + palLog(nullptr, " Dispatch base"); + } + + if (features & PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS) { + palLog(nullptr, " Null descriptors"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING) { + palLog(nullptr, " Indirect ray tracing"); + } + + if (features & PAL_ADAPTER_FEATURE_RAY_QUERY) { + palLog(nullptr, " Ray query"); + } + + palLog(nullptr, ""); + } + + // shutdown the graphics system + palShutdownGraphics(); + + // do any shutdown of the backend after graphics shutdown + + palFree(nullptr, adapters); + + return PAL_TRUE; +} \ No newline at end of file diff --git a/tests/graphics/descriptor_indexing_test.c b/tests/graphics/descriptor_indexing_test.c new file mode 100644 index 00000000..62b2b6e3 --- /dev/null +++ b/tests/graphics/descriptor_indexing_test.c @@ -0,0 +1,1151 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 +#define TEXTURE_WIDTH 128 +#define TEXTURE_HEIGHT 128 + +#define ARRAY_ELEMENT_0 17 +#define ARRAY_ELEMENT_1 47 +#define ARRAY_ELEMENT_2 55 +#define ARRAY_ELEMENT_3 78 + +// layout must match shader +typedef struct { + uint32_t textureIndices[4]; +} PushConstant; + +static void createFlatTexture( + uint32_t* texture, + uint32_t width, + uint32_t height, + uint8_t r, + uint8_t g, + uint8_t b) +{ + uint8_t* pixels = (uint8_t*)texture; + for (int32_t y = 0; y < height; ++y) { + for (int32_t x = 0; x < width; ++x) { + int32_t i = (y * width + x) * 4; + pixels[i + 0] = r; + pixels[i + 1] = g; + pixels[i + 2] = b; + pixels[i + 3] = 255; + } + } +} + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool descriptorIndexingTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[2]; + + PalBuffer* vertexBuffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + PalSampler* sampler = nullptr; + PalImage* textures[4]; + PalImageView* textureViews[4]; + PalBuffer* imageStagingBuffers[4]; + + PalDescriptorSetLayout* descriptorSetLayout = nullptr; + PalDescriptorPool* descriptorPool = nullptr; + PalDescriptorSet* descriptorSet = nullptr; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Descriptor Indexing Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + PalAdapterFeatures adapterFeatures; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + adapterFeatures = palGetAdapterFeatures(adapter); + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING)) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.4 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 4)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + features |= PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + if (adapterFeatures & PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS) { + features |= PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // get descriptor indexing capabilities + PalDescriptorIndexingCapabilities descriptorIndexingCaps = {0}; + palQueryDescriptorIndexingCapabilities(device, &descriptorIndexingCaps); + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create vertex and staging buffer + // clang-format off + float vertices[] = { + -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + + -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 1.0f}; + // clang-format on + + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = sizeof(vertices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_VERTEX; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &vertexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; // will send + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // map the staging buffer and upload the vertices + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, sizeof(vertices), &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + memcpy(ptr, vertices, sizeof(vertices)); + palUnmapBuffer(stagingBuffer); + + PalFence* fence = nullptr; + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // create image for the textures + PalImageCreateInfo imageCreateInfo = {0}; + imageCreateInfo.arrayLayerCount = 1; + imageCreateInfo.depth = 1; + imageCreateInfo.format = PAL_FORMAT_R8G8B8A8_UNORM; + imageCreateInfo.mipLevelCount = 1; // simple + imageCreateInfo.sampleCount = PAL_SAMPLE_COUNT_1; // simple + imageCreateInfo.type = PAL_IMAGE_TYPE_2D; + imageCreateInfo.usages = PAL_IMAGE_USAGE_TRANSFER_DST | PAL_IMAGE_USAGE_SAMPLED; + imageCreateInfo.width = TEXTURE_WIDTH; + imageCreateInfo.height = TEXTURE_HEIGHT; + imageCreateInfo.memoryUsage = PAL_IMAGE_MEMORY_USAGE_AUTO_GPU_ONLY; + + for (int i = 0; i < 4; i++) { + result = palCreateImage(device, &imageCreateInfo, &textures[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image"); + return PAL_FALSE; + } + } + + // create staging buffers to transfer the data to the images + PalBufferImageCopyInfo bufferImageCopyInfo = {0}; + bufferImageCopyInfo.ImageArrayLayerCount = 1; + bufferImageCopyInfo.imageWidth = TEXTURE_WIDTH; + bufferImageCopyInfo.imageHeight = TEXTURE_HEIGHT; + bufferImageCopyInfo.imageDepth = 1; // 2D image + + PalImageStagingRequirements stagingReq = {0}; + palComputeImageStagingRequirements( + device, + imageCreateInfo.format, + &bufferImageCopyInfo, + &stagingReq); + + // update our copy with the required buffer row length and buffer image height + bufferImageCopyInfo.bufferRowLength = stagingReq.bufferRowLength; + bufferImageCopyInfo.bufferImageHeight = stagingReq.bufferImageHeight; + + // create staging buffers to transfer the data to the image + uint32_t textureDatas[4][TEXTURE_WIDTH * TEXTURE_HEIGHT]; + createFlatTexture(textureDatas[0], TEXTURE_WIDTH, TEXTURE_HEIGHT, 255, 0, 0); + createFlatTexture(textureDatas[1], TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, 255, 0); + createFlatTexture(textureDatas[2], TEXTURE_WIDTH, TEXTURE_HEIGHT, 0, 0, 255); + createFlatTexture(textureDatas[3], TEXTURE_WIDTH, TEXTURE_HEIGHT, 255, 255, 0); + + PalBufferCreateInfo imageStagingBufferCreateInfo = {0}; + imageStagingBufferCreateInfo.size = stagingReq.bufferSize; + imageStagingBufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; + imageStagingBufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + + for (int i = 0; i < 4; i++) { + result = palCreateBuffer(device, &imageStagingBufferCreateInfo, &imageStagingBuffers[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // copy data + void* data = nullptr; + result = palMapBuffer(imageStagingBuffers[i], 0, stagingReq.bufferSize, &data); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + palWriteImageStaging( + device, + imageCreateInfo.format, + &bufferImageCopyInfo, + textureDatas[i], + data); + + palUnmapBuffer(imageStagingBuffers[i]); + } + + // use the first command buffer to upload the copy + // and reset it when done + // set a fence and check at the last line before the main loop + // to see if we have to wait for the copy to be executed + result = palCmdBegin(cmdBuffers[0], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = sizeof(vertices); + palCmdCopyBuffer(cmdBuffers[0], vertexBuffer, stagingBuffer, ©Info); + + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffers[0], vertexBuffer, &barrierInfo); + + // set a barrier on the image to transition it into transfer dst state + PalImageSubresourceRange textureRange = {0}; + textureRange.startMipLevel = 0; + textureRange.startArrayLayer = 0; + textureRange.mipLevelCount = 1; + textureRange.layerArrayCount = 1; + + for (int i = 0; i < 4; i++) { + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdImageBarrier(cmdBuffers[0], textures[i], &textureRange, &barrierInfo); + + palCmdCopyBufferToImage( + cmdBuffers[0], + textures[i], + imageStagingBuffers[i], + &bufferImageCopyInfo); + + // transition the image to shader read state + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_SHADER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_FRAGMENT_SHADER; + palCmdImageBarrier(cmdBuffers[0], textures[i], &textureRange, &barrierInfo); + } + + result = palCmdEnd(cmdBuffers[0]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[0]; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_TRANSFER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // now we have the checkerboard texture data in the image + // we need an image view and a sampler + PalImageViewCreateInfo checkerboardImageViewCreateInfo = {0}; + checkerboardImageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + checkerboardImageViewCreateInfo.subresourceRange = textureRange; + checkerboardImageViewCreateInfo.format = PAL_FORMAT_R8G8B8A8_UNORM; + + for (int i = 0; i < 4; i++) { + result = palCreateImageView( + device, + textures[i], + &checkerboardImageViewCreateInfo, + &textureViews[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + } + + // create sampler + PalSamplerCreateInfo samplerCreateInfo = {0}; + samplerCreateInfo.addressModeU = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeV = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeW = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.borderColor = PAL_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerCreateInfo.compareOp = PAL_COMPARE_OP_NEVER; // will not be used if its not enabled + + samplerCreateInfo.enableAnisotropy = PAL_FALSE; + samplerCreateInfo.enableCompare = PAL_FALSE; + samplerCreateInfo.magFilterMode = PAL_FILTER_MODE_LINEAR; + samplerCreateInfo.minFilterMode = PAL_FILTER_MODE_LINEAR; + samplerCreateInfo.maxAnisotropy = 1.0f; + + result = palCreateSampler(device, &samplerCreateInfo, &sampler); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create sampler"); + return PAL_FALSE; + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[2]; + PalShaderEntryInfo entries[2]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/texture_vert.spv"; + sources[1] = "graphics/shaders/bin/spirv/descriptor_indexing.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/texture_vert.dxil"; + sources[1] = "graphics/shaders/bin/dxil/descriptor_indexing.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_VERTEX; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + for (int i = 0; i < 2; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create descriptor set layout + // We use descriptor count of 100 and only use 4 slots + PalDescriptorSetLayoutBinding descriptorBindings[2]; + descriptorBindings[0].descriptorCount = 100; + descriptorBindings[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + descriptorBindings[1].descriptorCount = 1; // not an array + descriptorBindings[1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + + PalDescriptorSetLayoutCreateInfo descriptorSetLayoutcreateInfo = {0}; + descriptorSetLayoutcreateInfo.bindingCount = 2; + descriptorSetLayoutcreateInfo.bindings = descriptorBindings; + + // we only need the partially bound feature if supported. We dont use the other features + if (descriptorIndexingCaps.flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND) { + descriptorSetLayoutcreateInfo.flags |= PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND; + } + + result = + palCreateDescriptorSetLayout(device, &descriptorSetLayoutcreateInfo, &descriptorSetLayout); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor set layout"); + return PAL_FALSE; + } + + // create descriptor pool + PalDescriptorPoolBindingSize storageBufferBindingsizes[2]; + storageBufferBindingsizes[0].bindingCount = 100; + storageBufferBindingsizes[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + storageBufferBindingsizes[1].bindingCount = 1; + storageBufferBindingsizes[1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + + PalDescriptorPoolCreateInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.maxDescriptorSets = 1; // only one set + descriptorPoolCreateInfo.bindingSizeCount = 2; + descriptorPoolCreateInfo.bindingSizes = storageBufferBindingsizes; + descriptorPoolCreateInfo.flags = descriptorSetLayoutcreateInfo.flags; + + result = palCreateDescriptorPool(device, &descriptorPoolCreateInfo, &descriptorPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor pool"); + return PAL_FALSE; + } + + // allocate a single descriptor set from the descriptor pool + // using the layout we created above + result = palAllocateDescriptorSet(device, descriptorPool, descriptorSetLayout, &descriptorSet); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate descriptor set"); + return PAL_FALSE; + } + + // check if null descriptors was enabled and partially bound was not supported + PalDescriptorIndexingFlags flags = descriptorIndexingCaps.flags; + PalBool hasPartiallyBound = flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND; + if (adapterFeatures & PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS && !hasPartiallyBound) { + // write null descriptors into the slots + PalDescriptorSetWriteInfo writeInfo = {0}; + writeInfo.layoutBindingIndex = 0; + writeInfo.descriptorSet = descriptorSet; + writeInfo.descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writeInfo.descriptorCount = 100; + + writeInfo.arrayElement = 0; + writeInfo.samplerInfos = nullptr; + writeInfo.imageViewInfos = nullptr; + writeInfo.tlasInfos = nullptr; + writeInfo.bufferInfos = nullptr; + + result = palUpdateDescriptorSet(device, 1, &writeInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + } else { + // both null descriptors and partially bound are not supported + // we write the first texture to all slots + PalDescriptorImageViewInfo imageViewInfos[100]; + for (int i = 0; i < 100; i++) { + imageViewInfos[i].imageView = textureViews[0]; + } + + PalDescriptorSetWriteInfo writeInfo = {0}; + writeInfo.layoutBindingIndex = 0; + writeInfo.descriptorSet = descriptorSet; + writeInfo.descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writeInfo.descriptorCount = 100; + + writeInfo.arrayElement = 0; + writeInfo.samplerInfos = nullptr; + writeInfo.imageViewInfos = imageViewInfos; + writeInfo.tlasInfos = nullptr; + writeInfo.bufferInfos = nullptr; + + result = palUpdateDescriptorSet(device, 1, &writeInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + } + + // we only write 4 descriptors + uint32_t arrElements[] = {ARRAY_ELEMENT_0, ARRAY_ELEMENT_1, ARRAY_ELEMENT_2, ARRAY_ELEMENT_3}; + + PalDescriptorImageViewInfo descriptorImageInfos[4]; + descriptorImageInfos[0].imageView = textureViews[0]; + descriptorImageInfos[1].imageView = textureViews[1]; + descriptorImageInfos[2].imageView = textureViews[2]; + descriptorImageInfos[3].imageView = textureViews[3]; + + PalDescriptorSamplerInfo descriptorSamplerInfo = {0}; + descriptorSamplerInfo.sampler = sampler; + + PalDescriptorSetWriteInfo writeInfos[5]; + // sampler + writeInfos[0].layoutBindingIndex = 1; + writeInfos[0].samplerInfos = &descriptorSamplerInfo; + writeInfos[0].descriptorSet = descriptorSet; + writeInfos[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + writeInfos[0].descriptorCount = 1; + + writeInfos[0].arrayElement = 0; + writeInfos[0].imageViewInfos = nullptr; + writeInfos[0].tlasInfos = nullptr; + writeInfos[0].bufferInfos = nullptr; + + // textures + for (int i = 0; i < 4; i++) { + writeInfos[i + 1].layoutBindingIndex = 0; + writeInfos[i + 1].imageViewInfos = &descriptorImageInfos[i]; + writeInfos[i + 1].descriptorSet = descriptorSet; + writeInfos[i + 1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writeInfos[i + 1].descriptorCount = 1; + + writeInfos[i + 1].arrayElement = arrElements[i]; + writeInfos[i + 1].bufferInfos = nullptr; + writeInfos[i + 1].samplerInfos = nullptr; + writeInfos[i + 1].tlasInfos = nullptr; + } + + result = palUpdateDescriptorSet(device, 5, writeInfos); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + // create pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.descriptorSetLayoutCount = 1; + pipelineLayoutCreateInfo.descriptorSetLayouts = &descriptorSetLayout; + pipelineLayoutCreateInfo.usePushConstant = PAL_TRUE; + pipelineLayoutCreateInfo.pushConstantInfo.offset = 0; + pipelineLayoutCreateInfo.pushConstantInfo.size = sizeof(PushConstant); // must match shader + + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + PalVertexLayout vertexLayout = {0}; + PalVertexAttribute vertexAttributes[2]; + + // position + vertexAttributes[0].semanticID = PAL_VERTEX_SEMANTIC_ID_POSITION; + vertexAttributes[0].type = PAL_VERTEX_TYPE_FLOAT2; + + // texture coordinates + vertexAttributes[1].semanticID = PAL_VERTEX_SEMANTIC_ID_TEXCOORD; + vertexAttributes[1].type = PAL_VERTEX_TYPE_FLOAT2; + + vertexLayout.attributeCount = 2; + vertexLayout.attributes = vertexAttributes; + vertexLayout.binding = 0; // first vertex buffer binding slot + vertexLayout.type = PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX; + + pipelineCreateInfo.vertexLayoutCount = 1; + pipelineCreateInfo.vertexLayouts = &vertexLayout; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 2; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 2; i++) { + palDestroyShader(shaders[i]); + } + + // wait for the vertices copy to be done + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // the vertices have been copied + palDestroyFence(fence); + palDestroyBuffer(stagingBuffer); + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + PushConstant pushConstant = {0}; + pushConstant.textureIndices[0] = ARRAY_ELEMENT_0; + pushConstant.textureIndices[1] = ARRAY_ELEMENT_1; + pushConstant.textureIndices[2] = ARRAY_ELEMENT_2; + pushConstant.textureIndices[3] = ARRAY_ELEMENT_3; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdPushConstants(cmdBuffers[currentFrame], 0, sizeof(PushConstant), &pushConstant); + palCmdBindDescriptorSet(cmdBuffers[currentFrame], 0, descriptorSet); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + + uint64_t offset[] = {0}; + palCmdBindVertexBuffers(cmdBuffers[currentFrame], 0, 1, &vertexBuffer, offset); + palCmdDraw(cmdBuffers[currentFrame], 6, 1, 0, 0); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyDescriptorPool(descriptorPool); + palDestroyDescriptorSetLayout(descriptorSetLayout); + + palDestroySampler(sampler); + for (int i = 0; i < 4; i++) { + palDestroyImageView(textureViews[i]); + palDestroyImage(textures[i]); + palDestroyBuffer(imageStagingBuffers[i]); + } + + palDestroyBuffer(vertexBuffer); + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/graphics/geometry_test.c b/tests/graphics/geometry_test.c new file mode 100644 index 00000000..95bc4610 --- /dev/null +++ b/tests/graphics/geometry_test.c @@ -0,0 +1,665 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool geometryTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[3]; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Geometry Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + PalAdapterFeatures adapterFeatures; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + adapterFeatures = palGetAdapterFeatures(adapter); + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_INDIRECT_DRAW)) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + features |= PAL_ADAPTER_FEATURE_GEOMETRY_SHADER; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[3]; + PalShaderEntryInfo entries[3]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/geometry_vert.spv"; + sources[1] = "graphics/shaders/bin/spirv/triangle_frag.spv"; + sources[2] = "graphics/shaders/bin/spirv/geometry.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/geometry_vert.dxil"; + sources[1] = "graphics/shaders/bin/dxil/triangle_frag.dxil"; + sources[2] = "graphics/shaders/bin/dxil/geometry.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_VERTEX; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + entries[2].stage = PAL_SHADER_STAGE_GEOMETRY; + entries[2].entryName = "main"; + entries[2].patchControlPoints = 0; + + for (int i = 0; i < 3; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create a pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 3; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 3; i++) { + palDestroyShader(shaders[i]); + } + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + PalBarrierInfo barrierInfo = {0}; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + palCmdDraw(cmdBuffers[currentFrame], 3, 1, 0, 0); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} \ No newline at end of file diff --git a/tests/graphics/graphics_test.c b/tests/graphics/graphics_test.c new file mode 100644 index 00000000..86ad96dc --- /dev/null +++ b/tests/graphics/graphics_test.c @@ -0,0 +1,597 @@ + +#include "pal2/pal_graphics.h" +#include "tests.h" + +PalBool graphicsTest() +{ + // initialize the graphics system + PalResult result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t count = 0; + result = palEnumerateAdapters(&count, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get query adapters"); + return PAL_FALSE; + } + + if (count == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %u", count); + + // allocate an array of adapters or use a fixed array + // Example: PalAdapter* adapters[12]; + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * count, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&count, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get query adapters"); + return PAL_FALSE; + } + + // get information about all the adapters + PalAdapterInfo info; + PalAdapterCapabilities caps; + PalAdapterFeatures features = 0; + for (int32_t i = 0; i < count; i++) { + PalAdapter* adapter = adapters[i]; + palGetAdapterInfo(adapter, &info); + palGetAdapterCapabilities(adapter, &caps); + + // create a device + features = palGetAdapterFeatures(adapter); + PalDevice* device = nullptr; + PalAdapterFeatures deviceFeatures = 0; + if (features & PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY) { + deviceFeatures |= PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY; + } + + if (features & PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE) { + deviceFeatures |= PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE; + } + + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) { + deviceFeatures |= PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE; + } + + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + deviceFeatures |= PAL_ADAPTER_FEATURE_MESH_SHADER; + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + deviceFeatures |= PAL_ADAPTER_FEATURE_RAY_TRACING; + } + + if (features & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING) { + deviceFeatures |= PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING; + } + + result = palCreateDevice(adapter, deviceFeatures, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + palFree(nullptr, adapters); + return PAL_FALSE; + } + + uint64_t vramMib = (uint64_t)info.vram / (1024 * 1024); + uint64_t sharedMemMib = (uint64_t)info.sharedMemory / (1024 * 1024); + + palLog(nullptr, "GPU Name: %s", info.name); + palLog(nullptr, " Backend Name: %s", info.backendName); + palLog(nullptr, " Driver Version: %llu", info.driverVersion); + palLog(nullptr, " Vram %llu MIB", vramMib); + palLog(nullptr, " Shared Memory %llu MIB", sharedMemMib); + palLog(nullptr, " Device Id: %u", info.deviceId); + palLog(nullptr, " Vendor Id: %u", info.vendorId); + + const char* typeString; + switch (info.type) { + case PAL_ADAPTER_TYPE_INTEGRATED: { + typeString = "Integrated"; + break; + } + + case PAL_ADAPTER_TYPE_VIRTUAL: { + typeString = "Virtual"; + break; + } + + case PAL_ADAPTER_TYPE_DISCRETE: { + typeString = "Discrete"; + break; + } + + case PAL_ADAPTER_TYPE_CPU: { + typeString = "CPU"; + break; + } + } + palLog(nullptr, " Type: %s", typeString); + + // we check for types that are core to PAL since we are not adding any custom backends + const char* apiTypeString; + switch (info.apiType) { + case PAL_ADAPTER_API_TYPE_D3D12: { + apiTypeString = "D3D12"; + break; + } + + case PAL_ADAPTER_API_TYPE_VULKAN: { + apiTypeString = "Vulkan"; + break; + } + + case PAL_ADAPTER_API_TYPE_METAL: { + apiTypeString = "Metal"; + break; + } + } + palLog(nullptr, " API Type: %s", apiTypeString); + + palLog(nullptr, ""); + palLog(nullptr, " Capabilities:"); + palLog(nullptr, " Max compute queue: %u", caps.maxComputeQueues); + palLog(nullptr, " Max graphics queue: %u", caps.maxGraphicsQueues); + palLog(nullptr, " Max copy queue: %u", caps.maxCopyQueues); + palLog(nullptr, " Max color attachments: %u", caps.maxColorAttachments); + palLog(nullptr, " Max uniform buffer size: %u Bytes", caps.maxUniformBufferSize); + + palLog(nullptr, " Max storage buffer size: %u Bytes", caps.maxStorageBufferSize); + palLog(nullptr, " Max push constant size: %u Bytes", caps.maxPushConstantSize); + palLog(nullptr, " Max vertex layouts: %u", caps.maxVertexLayouts); + palLog(nullptr, " Max vertex attributes: %u", caps.maxVertexAttributes); + palLog(nullptr, " Max tessellation patch point: %u", caps.maxTessellationPatchPoint); + + palLog(nullptr, ""); + palLog(nullptr, " Viewport Capabilities:"); + palLog(nullptr, " Max width: %u", caps.viewportCaps.maxWidth); + palLog(nullptr, " Max height: %u", caps.viewportCaps.maxHeight); + palLog(nullptr, " Min bounds range: %.1f", caps.viewportCaps.minBoundsRange); + palLog(nullptr, " Max bounds range: %.1f", caps.viewportCaps.maxBoundsRange); + + palLog(nullptr, ""); + palLog(nullptr, " Image Capabilities:"); + palLog(nullptr, " Max width: %u", caps.imageCaps.maxWidth); + palLog(nullptr, " Max height: %u", caps.imageCaps.maxHeight); + palLog(nullptr, " Max depth: %u", caps.imageCaps.maxDepth); + palLog(nullptr, " Max array layers: %u", caps.imageCaps.maxArrayLayers); + palLog(nullptr, " Max mip levels: %u", caps.imageCaps.maxMipLevels); + + palLog(nullptr, ""); + palLog(nullptr, " Resource Capabilities:"); + PalResourceCapabilities* resourceCaps = &caps.resourceCaps; + + // clang-format off + palLog(nullptr, " Max per stage sampled images: %u", resourceCaps->maxPerStageSampledImages); + palLog(nullptr, " Max per set sampled images: %u", resourceCaps->maxPerSetSampledImages); + palLog(nullptr, " Max per stage storage images: %u", resourceCaps->maxPerStageStorageImages); + palLog(nullptr, " Max per set storage images: %u", resourceCaps->maxPerSetStorageImages); + + palLog(nullptr, " Max per stage samplers: %u", resourceCaps->maxPerStageSamplers); + palLog(nullptr, " Max per set samplers: %u", resourceCaps->maxPerSetSamplers); + palLog(nullptr, " Max per stage storage buffers: %u", resourceCaps->maxPerStageStorageBuffers); + palLog(nullptr, " Max per set storage buffers: %u", resourceCaps->maxPerSetStorageBuffers); + + palLog(nullptr, " Max per stage uniform buffers: %u", resourceCaps->maxPerStageUniformBuffers); + palLog(nullptr, " Max per set uniform buffers: %u", resourceCaps->maxPerSetUniformBuffers); + palLog(nullptr, " Max per stage acceleration structures: %u", resourceCaps->maxPerStageAccelerationStructure); + palLog(nullptr, " Max per set acceleration structures: %u", resourceCaps->maxPerSetAccelerationStructure); + palLog(nullptr, " Max bound sets: %u", resourceCaps->maxBoundSets); + // clang-format on + + palLog(nullptr, ""); + palLog(nullptr, " Compute Capabilities:"); + palLog(nullptr, " Max invocations: %u", caps.computeCaps.maxWorkGroupInvocations); + palLog(nullptr, " Max work group count[0]: %u", caps.computeCaps.maxWorkGroupCount[0]); + palLog(nullptr, " Max work group count[1]: %u", caps.computeCaps.maxWorkGroupCount[1]); + palLog(nullptr, " Max work group count[2]: %u", caps.computeCaps.maxWorkGroupCount[2]); + palLog(nullptr, " Max work group size[0]: %u", caps.computeCaps.maxWorkGroupSize[0]); + palLog(nullptr, " Max work group size[1]: %u", caps.computeCaps.maxWorkGroupSize[1]); + palLog(nullptr, " Max work group size[2]: %u", caps.computeCaps.maxWorkGroupSize[2]); + + // shader formats + uint32_t target; + uint32_t targetMajor = 0; + uint32_t targetMinor = 0; + + palLog(nullptr, ""); + palLog(nullptr, " Supported Shader Formats:"); + if (info.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + palLog(nullptr, " SPIRV"); + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + targetMajor = PAL_SHADER_TARGET_MAJOR(target); + targetMinor = PAL_SHADER_TARGET_MINOR(target); + + palLog(nullptr, " Highest Spirv Target: %u.%u", targetMajor, targetMinor); + palLog(nullptr, ""); + } + + if (info.shaderFormats & PAL_SHADER_FORMAT_DXBC) { + palLog(nullptr, " DXBC"); + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXBC); + targetMajor = PAL_SHADER_TARGET_MAJOR(target); + targetMinor = PAL_SHADER_TARGET_MINOR(target); + + palLog(nullptr, " Highest Dxbc Target: %u.%u", targetMajor, targetMinor); + palLog(nullptr, ""); + } + + if (info.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + palLog(nullptr, " DXIL"); + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + targetMajor = PAL_SHADER_TARGET_MAJOR(target); + targetMinor = PAL_SHADER_TARGET_MINOR(target); + + palLog(nullptr, " Highest Dxil Target: %u.%u", targetMajor, targetMinor); + palLog(nullptr, ""); + } + + // features + palLog(nullptr, ""); + palLog(nullptr, " Supported Features:"); + if (features & PAL_ADAPTER_FEATURE_SAMPLER_ANISOTROPY) { + palLog(nullptr, " Sampler Anisotropy"); + + PalSamplerAnisotropyCapabilities tmp; + palQuerySamplerAnisotropyCapabilities(device, &tmp); + + palLog(nullptr, " Max anisotropy: %u", tmp.maxAnisotropy); + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEWPORT) { + palLog(nullptr, " Multi viewport"); + + PalMultiViewportCapabilities tmp; + palQueryMultiViewportCapabilities(device, &tmp); + + palLog(nullptr, " Max count: %u", tmp.maxCount); + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_RAY_TRACING) { + palLog(nullptr, " Ray tracing"); + + PalRayTracingCapabilities tmp; + palQueryRayTracingCapabilities(device, &tmp); + + palLog(nullptr, " Max recursion depth: %u", tmp.maxRecursionDepth); + palLog(nullptr, " Max hit attribute size: %u Bytes", tmp.maxHitAttributeSize); + palLog(nullptr, " Max instance count: %u", tmp.maxInstanceCount); + palLog(nullptr, " Max primitive count: %u", tmp.maxPrimitiveCount); + palLog(nullptr, " Max geometry count: %u", tmp.maxGeometryCount); + palLog(nullptr, " Max payload size: %u Bytes", tmp.maxPayloadSize); + palLog(nullptr, " Max dispatch invocations: %u", tmp.maxDispatchInvocations); + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_MESH_SHADER) { + palLog(nullptr, " Mesh and task shader"); + + PalMeshShaderCapabilities tmp; + palQueryMeshShaderCapabilities(device, &tmp); + + palLog(nullptr, " Max output primitives: %u", tmp.maxOutputPrimitives); + palLog(nullptr, " Max output vertices: %u", tmp.maxOutputVertices); + palLog(nullptr, " Max invocations: %u", tmp.maxWorkGroupInvocations); + palLog(nullptr, " Max task invocations: %u", tmp.maxTaskWorkGroupInvocations); + + palLog(nullptr, " Max work group count[0]: %u", tmp.maxWorkGroupCount[0]); + palLog(nullptr, " Max work group count[1]: %u", tmp.maxWorkGroupCount[1]); + palLog(nullptr, " Max work group count[2]: %u", tmp.maxWorkGroupCount[2]); + palLog(nullptr, " Max task work group count[0]: %u", tmp.maxTaskWorkGroupCount[0]); + palLog(nullptr, " Max task work group count[1]: %u", tmp.maxTaskWorkGroupCount[1]); + palLog(nullptr, " Max task work group count[2]: %u", tmp.maxTaskWorkGroupCount[2]); + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE) { + palLog(nullptr, " Fragment shading rate"); + + PalFragmentShadingRateCapabilities tmp; + palQueryFragmentShadingRateCapabilities(device, &tmp); + + palLog(nullptr, " Min texel width: %u", tmp.minTexelWidth); + palLog(nullptr, " Min texel height: %u", tmp.maxTexelWidth); + palLog(nullptr, " Max texel width: %u", tmp.minTexelHeight); + palLog(nullptr, " Max texel height: %u", tmp.maxTexelHeight); + + palLog(nullptr, " Supported Shading Rates:"); + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_1X1)) { + palLog(nullptr, " 1 X 1"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_1X2)) { + palLog(nullptr, " 1 X 2"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_2X1)) { + palLog(nullptr, " 2 X 1"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_2X2)) { + palLog(nullptr, " 2 X 2"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_2X4)) { + palLog(nullptr, " 2 X 4"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_4X2)) { + palLog(nullptr, " 4 X 2"); + } + + if (palIsSupported(tmp.supportedShadingRates, PAL_FRAGMENT_SHADING_RATE_4X4)) { + palLog(nullptr, " 4 X 4"); + } + + palLog(nullptr, " Supported Combiner operations:"); + // clang-format off + if (palIsSupported(tmp.supportedCombinerOps, PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP)) { + palLog(nullptr, " Keep"); + } + + if (palIsSupported(tmp.supportedCombinerOps, PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE)) { + palLog(nullptr, " Replace"); + } + + if (palIsSupported(tmp.supportedCombinerOps, PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN)) { + palLog(nullptr, " Min"); + } + + if (palIsSupported(tmp.supportedCombinerOps, PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX)) { + palLog(nullptr, " Max"); + } + + if (palIsSupported(tmp.supportedCombinerOps, PAL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL)) { + palLog(nullptr, " Mul"); + } + // clang-format on + + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_DESCRIPTOR_INDEXING) { + palLog(nullptr, " Descriptor indexing"); + + PalDescriptorIndexingCapabilities tmp; + palQueryDescriptorIndexingCapabilities(device, &tmp); + + palLog(nullptr, " Supported flags:"); + if (tmp.flags & PAL_DESCRIPTOR_INDEXING_FLAG_UPDATE_AFTER_BIND) { + palLog(nullptr, " Update after bind: True"); + } else { + palLog(nullptr, " Update after bind: False"); + } + + if (tmp.flags & PAL_DESCRIPTOR_INDEXING_FLAG_PARTIALLY_BOUND) { + palLog(nullptr, " Partially bound: True"); + } else { + palLog(nullptr, " Partially bound: False"); + } + + if (tmp.flags & PAL_DESCRIPTOR_INDEXING_FLAG_NON_UNIFORM_INDEXING) { + palLog(nullptr, " Non uniform indexing: True"); + } else { + palLog(nullptr, " Non uniform indexing: False"); + } + + // clang-format off + palLog(nullptr, " Max per stage sampled images: %u", tmp.maxPerStageSampledImages); + palLog(nullptr, " Max per set sampled images: %u", tmp.maxPerSetSampledImages); + palLog(nullptr, " Max per stage storage images: %u", tmp.maxPerStageStorageImages); + palLog(nullptr, " Max per set storage images: %u", tmp.maxPerSetStorageImages); + + palLog(nullptr, " Max per stage samplers: %u", tmp.maxPerStageSamplers); + palLog(nullptr, " Max per set samplers: %u", tmp.maxPerSetSamplers); + palLog(nullptr, " Max per stage storage buffers: %u", tmp.maxPerStageStorageBuffers); + palLog(nullptr, " Max per set storage buffers: %u", tmp.maxPerSetStorageBuffers); + + palLog(nullptr, " Max per stage uniform buffers: %u", tmp.maxPerStageUniformBuffers); + palLog(nullptr, " Max per set uniform buffers: %u", tmp.maxPerSetUniformBuffers); + palLog(nullptr, " Max per stage acceleration structures: %u", tmp.maxPerStageAccelerationStructure); + palLog(nullptr, " Max per set acceleration structures: %u", tmp.maxPerSetAccelerationStructure); + // clang-format on + + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_MULTI_VIEW) { + palLog(nullptr, " Multiview"); + + PalMultiViewCapabilities tmp; + palQueryMultiViewCapabilities(device, &tmp); + + palLog(nullptr, " Max view count: %u", tmp.maxViewCount); + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_DEPTH_STENCIL_RESOLVE) { + palLog(nullptr, " Depth stencil resolve"); + + PalDepthStencilCapabilities tmp; + palQueryDepthStencilCapabilities(device, &tmp); + + if (tmp.supportsIndependentResolve) { + palLog(nullptr, " Independent resolve: True"); + } else { + palLog(nullptr, " Independent resolve: False"); + } + + if (tmp.supportsIndependentResolveNone) { + palLog(nullptr, " Independent resolve none: True"); + } else { + palLog(nullptr, " Independent resolve none: False"); + } + + palLog(nullptr, " Supported depth resolves modes:"); + if (palIsSupported(tmp.supportedDepthResolveModes, PAL_RESOLVE_MODE_SAMPLE_ZERO)) { + palLog(nullptr, " Zero"); + } + + if (palIsSupported(tmp.supportedDepthResolveModes, PAL_RESOLVE_MODE_AVERAGE)) { + palLog(nullptr, " Average"); + } + + if (palIsSupported(tmp.supportedDepthResolveModes, PAL_RESOLVE_MODE_MIN)) { + palLog(nullptr, " Min"); + } + + if (palIsSupported(tmp.supportedDepthResolveModes, PAL_RESOLVE_MODE_MAX)) { + palLog(nullptr, " Max"); + } + + palLog(nullptr, " Supported stencil resolve modes:"); + if (palIsSupported(tmp.supportedStencilResolveModes, PAL_RESOLVE_MODE_SAMPLE_ZERO)) { + palLog(nullptr, " Zero"); + } + + if (palIsSupported(tmp.supportedStencilResolveModes, PAL_RESOLVE_MODE_AVERAGE)) { + palLog(nullptr, " Average"); + } + + if (palIsSupported(tmp.supportedStencilResolveModes, PAL_RESOLVE_MODE_MIN)) { + palLog(nullptr, " Min"); + } + + if (palIsSupported(tmp.supportedStencilResolveModes, PAL_RESOLVE_MODE_MAX)) { + palLog(nullptr, " Max"); + } + + palLog(nullptr, ""); + } + + if (features & PAL_ADAPTER_FEATURE_SWAPCHAIN) { + palLog(nullptr, " Swapchain"); + } + + if (features & PAL_ADAPTER_FEATURE_SAMPLE_RATE_SHADING) { + palLog(nullptr, " Sample rate shading"); + } + + if (features & PAL_ADAPTER_FEATURE_TIMELINE_SEMAPHORE) { + palLog(nullptr, " Timeline Semaphore"); + } + + if (features & PAL_ADAPTER_FEATURE_TESSELLATION_SHADER) { + palLog(nullptr, " Tesselation Shader"); + } + + if (features & PAL_ADAPTER_FEATURE_GEOMETRY_SHADER) { + palLog(nullptr, " Geometry shader"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT16) { + palLog(nullptr, " Shader float16"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_FLOAT64) { + palLog(nullptr, " Shader float64"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT16) { + palLog(nullptr, " Shader int16"); + } + + if (features & PAL_ADAPTER_FEATURE_SHADER_INT64) { + palLog(nullptr, " Shader int64"); + } + + if (features & PAL_ADAPTER_FEATURE_IMAGE_VIEW_CUBE_ARRAY) { + palLog(nullptr, " Image view type Cube array"); + } + + if (features & PAL_ADAPTER_FEATURE_FENCE_RESET) { + palLog(nullptr, " Resetting fence"); + } + + if (features & PAL_ADAPTER_FEATURE_POLYGON_MODE_LINE) { + palLog(nullptr, " Polygon mode line"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_CULL_MODE) { + palLog(nullptr, " Dynamic cull mode"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_FRONT_FACE) { + palLog(nullptr, " Dynamic front face"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_PRIMITIVE_TOPOLOGY) { + palLog(nullptr, " Dynamic primitive topology"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_TEST_ENABLE) { + palLog(nullptr, " Dynamic depth test enable"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_DEPTH_WRITE_ENABLE) { + palLog(nullptr, " Dynamic depth write enable"); + } + + if (features & PAL_ADAPTER_FEATURE_DYNAMIC_STENCIL_OP) { + palLog(nullptr, " Dynamic stencil op"); + } + + if (features & PAL_ADAPTER_FEATURE_FRAGMENT_SHADING_RATE_ATTACHMENT) { + palLog(nullptr, " Fragment shading rate attachment"); + } + + if (features & PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS) { + palLog(nullptr, " Buffer device address"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW) { + palLog(nullptr, " Indirect draw"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_COUNT) { + palLog(nullptr, " Indirect draw count"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH) { + palLog(nullptr, " Indirect mesh draw"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_DRAW_MESH_COUNT) { + palLog(nullptr, " Indirect mesh draw count"); + } + + if (features & PAL_ADAPTER_FEATURE_DISPATCH_BASE) { + palLog(nullptr, " Dispatch base"); + } + + if (features & PAL_ADAPTER_FEATURE_NULL_DESCRIPTORS) { + palLog(nullptr, " Null descriptors"); + } + + if (features & PAL_ADAPTER_FEATURE_INDIRECT_RAY_TRACING) { + palLog(nullptr, " Indirect ray tracing"); + } + + if (features & PAL_ADAPTER_FEATURE_RAY_QUERY) { + palLog(nullptr, " Ray query"); + } + + palLog(nullptr, ""); + palDestroyDevice(device); + } + + // shutdown the graphics system + palShutdownGraphics(); + + palFree(nullptr, adapters); + + return PAL_TRUE; +} diff --git a/tests/graphics/indirect_draw_test.c b/tests/graphics/indirect_draw_test.c new file mode 100644 index 00000000..6c36042d --- /dev/null +++ b/tests/graphics/indirect_draw_test.c @@ -0,0 +1,871 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 + +static inline uint32_t align( + uint32_t value, + uint32_t alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool indirectDrawTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[2]; + + PalBuffer* vertexBuffer = nullptr; + PalBuffer* indexBuffer = nullptr; + PalBuffer* indirectBuffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Indirect Draw Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + PalAdapterFeatures adapterFeatures; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + adapterFeatures = palGetAdapterFeatures(adapter); + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_INDIRECT_DRAW)) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + features |= PAL_ADAPTER_FEATURE_INDIRECT_DRAW; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create vertex, index, indirect and staging buffer + // clang-format off + float vertices[] = { + -0.5, 0.5, 1.0f, 1.0f, 0.0f, + 0.5, 0.5, 1.0f, 1.0f, 0.0f, + 0.5,-0.5, 1.0f, 1.0f, 0.0f, + -0.5,-0.5, 1.0f, 1.0f, 0.0f + }; + // clang-format on + + uint32_t indices[6] = {0, 1, 2, 2, 3, 0}; + + // vertex buffer + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = sizeof(vertices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_VERTEX; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &vertexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // index buffer + bufferCreateInfo.size = sizeof(indices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_INDEX; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + result = palCreateBuffer(device, &bufferCreateInfo, &indexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // indirect buffer + bufferCreateInfo.size = sizeof(PalDrawIndexedIndirectData); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_INDIRECT; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + result = palCreateBuffer(device, &bufferCreateInfo, &indirectBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // staging buffer for vertex, index and indirect buffer + uint32_t offset = 0; + uint32_t indirectOffset = offset; + offset += sizeof(PalDrawIndexedIndirectData); + offset = align(offset, 16); + + uint32_t indexOffset = offset; + offset += sizeof(indices); + offset = align(offset, 16); + + uint32_t vertexOffset = offset; + offset += sizeof(vertices); + offset = align(offset, 16); + + uint32_t stagingBufferSize = offset; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; // will send + bufferCreateInfo.size = offset; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // map the staging buffer and upload the data + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, stagingBufferSize, &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + // copy indirect data + PalDrawIndexedIndirectData indirectData = {0}; + indirectData.firstIndex = 0; + indirectData.firstInstance = 0; + indirectData.indexCount = 6; + indirectData.instanceCount = 1; + indirectData.vertexOffset = 0; + + uint8_t* dst = (uint8_t*)ptr; + memcpy(dst + indirectOffset, &indirectData, sizeof(PalDrawIndexedIndirectData)); + + // copy vertices + memcpy(dst + vertexOffset, vertices, sizeof(vertices)); + + // copy indices + memcpy(dst + indexOffset, indices, sizeof(indices)); + + palUnmapBuffer(stagingBuffer); + + PalFence* fence = nullptr; + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // use the first command buffer to upload the copy + // and reset it when done + // set a fence and check at the last line before the main loop + // to see if we have to wait for the copy to be executed + result = palCmdBegin(cmdBuffers[0], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + + // copy to indirect buffer + PalBufferCopyInfo indirectCopyInfo = {0}; + indirectCopyInfo.dstOffset = 0; + indirectCopyInfo.size = sizeof(PalDrawIndexedIndirectData); + indirectCopyInfo.srcOffset = indirectOffset; + + palCmdCopyBuffer(cmdBuffers[0], indirectBuffer, stagingBuffer, &indirectCopyInfo); + palCmdBufferBarrier(cmdBuffers[0], indirectBuffer, &barrierInfo); + + PalBufferCopyInfo vertexCopyInfo = {0}; + vertexCopyInfo.dstOffset = 0; + vertexCopyInfo.size = sizeof(vertices); + vertexCopyInfo.srcOffset = vertexOffset; + palCmdCopyBuffer(cmdBuffers[0], vertexBuffer, stagingBuffer, &vertexCopyInfo); + palCmdBufferBarrier(cmdBuffers[0], vertexBuffer, &barrierInfo); + + // copy to index buffer + PalBufferCopyInfo indexCopyInfo = {0}; + indexCopyInfo.dstOffset = 0; + indexCopyInfo.size = sizeof(indices); + indexCopyInfo.srcOffset = indexOffset; + palCmdCopyBuffer(cmdBuffers[0], indexBuffer, stagingBuffer, &indexCopyInfo); + palCmdBufferBarrier(cmdBuffers[0], indexBuffer, &barrierInfo); + + result = palCmdEnd(cmdBuffers[0]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[0]; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_TRANSFER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[2]; + PalShaderEntryInfo entries[2]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/triangle_vert.spv"; + sources[1] = "graphics/shaders/bin/spirv/triangle_frag.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/triangle_vert.dxil"; + sources[1] = "graphics/shaders/bin/dxil/triangle_frag.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_VERTEX; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + for (int i = 0; i < 2; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + PalVertexLayout vertexLayout = {0}; + PalVertexAttribute vertexAttributes[2]; + + // position + vertexAttributes[0].semanticID = PAL_VERTEX_SEMANTIC_ID_POSITION; + vertexAttributes[0].type = PAL_VERTEX_TYPE_FLOAT2; + + // color coordinates + vertexAttributes[1].semanticID = PAL_VERTEX_SEMANTIC_ID_COLOR; + vertexAttributes[1].type = PAL_VERTEX_TYPE_FLOAT3; + + vertexLayout.attributeCount = 2; + vertexLayout.attributes = vertexAttributes; + vertexLayout.binding = 0; // first vertex buffer binding slot + vertexLayout.type = PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX; + + pipelineCreateInfo.vertexLayoutCount = 1; + pipelineCreateInfo.vertexLayouts = &vertexLayout; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 2; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 2; i++) { + palDestroyShader(shaders[i]); + } + + // wait for the vertices copy to be done + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // the vertices have been copied + palDestroyFence(fence); + palDestroyBuffer(stagingBuffer); + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + + uint64_t offset[] = {0}; + palCmdBindVertexBuffers(cmdBuffers[currentFrame], 0, 1, &vertexBuffer, offset); + palCmdBindIndexBuffer(cmdBuffers[currentFrame], indexBuffer, 0, PAL_INDEX_TYPE_UINT32); + palCmdDrawIndexedIndirect(cmdBuffers[currentFrame], indirectBuffer, 1); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyBuffer(indirectBuffer); + palDestroyBuffer(vertexBuffer); + palDestroyBuffer(indexBuffer); + + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/graphics/mesh_test.c b/tests/graphics/mesh_test.c new file mode 100644 index 00000000..ed453f87 --- /dev/null +++ b/tests/graphics/mesh_test.c @@ -0,0 +1,664 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool meshTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[2]; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Mesh Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterFeatures adapterFeatures = 0; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + adapterFeatures = palGetAdapterFeatures(adapter); + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_MESH_SHADER)) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.5 or dxil 6.5 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 5)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 5)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + features |= PAL_ADAPTER_FEATURE_MESH_SHADER; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[2]; + PalShaderEntryInfo entries[2]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/mesh.spv"; + sources[1] = "graphics/shaders/bin/spirv/triangle_frag.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/mesh.dxil"; + sources[1] = "graphics/shaders/bin/dxil/triangle_frag.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_MESH; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + for (int i = 0; i < 2; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create a pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 2; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 2; i++) { + palDestroyShader(shaders[i]); + } + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + PalBarrierInfo barrierInfo = {0}; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + + // draw a single triangle with the mesh shader + // palBuildWorkGroupInfo() is a helper to build + // the workgroup count per axis using normal + // workCount (image size, buffer size) + palCmdDrawMeshTasks(cmdBuffers[currentFrame], 1, 1, 1); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/graphics/multi_descriptor_set_test.c b/tests/graphics/multi_descriptor_set_test.c new file mode 100644 index 00000000..4764068b --- /dev/null +++ b/tests/graphics/multi_descriptor_set_test.c @@ -0,0 +1,515 @@ + +#include "pal2/pal_graphics.h" +#include "tests.h" + +#define BUFFER_SIZE 400 + +// layout must match shader +typedef struct { + uint32_t width; + uint32_t height; + uint32_t _padding[2]; + float set1Color[4]; + float set2Color[4]; + float set3Color[4]; +} PushConstant; + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool multiDescriptorSetTest() +{ + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalQueue* queue = nullptr; + PalCommandPool* cmdPool = nullptr; + PalCommandBuffer* cmdBuffer; + PalShader* shader = nullptr; + + PalBuffer* buffers[3]; + PalBuffer* stagingBuffers[3]; + + PalDescriptorPool* descriptorPool = nullptr; + PalDescriptorSetLayout* descriptorSetLayout; + PalDescriptorSet* descriptorSets[3]; + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalFence* fence = nullptr; + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + PalResult result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterFeatures adapterFeatures = 0; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxComputeQueues == 0) { + adapter = nullptr; + continue; + } + + // we want an adapter that supports the required bound descriptor sets (3) + if (caps.resourceCaps.maxBoundSets < 3) { + adapter = nullptr; + continue; + } + + // we want an adapter that supports the required push constant size (64 bytes) + if (caps.maxPushConstantSize < 64) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + result = palCreateDevice(adapter, 0, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create a compute command queue + result = palCreateQueue(device, PAL_QUEUE_TYPE_COMPUTE, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer(device, cmdPool, PAL_COMMAND_BUFFER_TYPE_PRIMARY, &cmdBuffer); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + + // create a compute shader + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* source = nullptr; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + source = "graphics/shaders/bin/spirv/compute_multi.spv"; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + source = "graphics/shaders/bin/dxil/compute_multi.dxil"; + } + + // read file + if (!readFile(source, nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(source, bytecode, &bytecodeSize); + + PalShaderEntryInfo computeEntry = {0}; + computeEntry.entryName = "main"; + computeEntry.stage = PAL_SHADER_STAGE_COMPUTE; + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &computeEntry; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shader); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + + // create a storage buffer + uint32_t bufferBytes = BUFFER_SIZE * BUFFER_SIZE * sizeof(float) * 4; // must match shader + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = bufferBytes; + + for (int i = 0; i < 3; i++) { + bufferCreateInfo.usages = PAL_BUFFER_USAGE_STORAGE | PAL_BUFFER_USAGE_TRANSFER_SRC; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + result = palCreateBuffer(device, &bufferCreateInfo, &buffers[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_DST; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffers[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + } + + // create descriptor set layout + PalDescriptorSetLayoutBinding descriptorBinding = {0}; + descriptorBinding.descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorBinding.descriptorCount = 1; // not an array + + PalDescriptorSetLayoutCreateInfo descriptorSetLayoutcreateInfo = {0}; + descriptorSetLayoutcreateInfo.bindingCount = 1; + descriptorSetLayoutcreateInfo.bindings = &descriptorBinding; + + result = + palCreateDescriptorSetLayout(device, &descriptorSetLayoutcreateInfo, &descriptorSetLayout); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor set layout"); + return PAL_FALSE; + } + + // create descriptor pool + PalDescriptorPoolBindingSize storageBufferBindingsize = {0}; + storageBufferBindingsize.bindingCount = 3; // across all sets + storageBufferBindingsize.descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + PalDescriptorPoolCreateInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.maxDescriptorSets = 3; + descriptorPoolCreateInfo.bindingSizeCount = 1; // one binding type + descriptorPoolCreateInfo.bindingSizes = &storageBufferBindingsize; + + result = palCreateDescriptorPool(device, &descriptorPoolCreateInfo, &descriptorPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor pool"); + return PAL_FALSE; + } + + // allocate the sets from the descriptor pool + for (int i = 0; i < 3; i++) { + result = palAllocateDescriptorSet( + device, + descriptorPool, + descriptorSetLayout, + &descriptorSets[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate descriptor set"); + return PAL_FALSE; + } + } + + // write the inital data to the descriptor set since its created empty + PalDescriptorBufferInfo descriptorBufferInfos[3]; + for (int i = 0; i < 3; i++) { + descriptorBufferInfos[i].buffer = buffers[i]; + descriptorBufferInfos[i].offset = 0; + descriptorBufferInfos[i].size = bufferBytes; + descriptorBufferInfos[i].stride = 16; // sizeof(vec4) or float4. + } + + PalDescriptorSetWriteInfo writeInfos[3]; + for (int i = 0; i < 3; i++) { + writeInfos[i].layoutBindingIndex = 0; // single descriptor binding + writeInfos[i].bufferInfos = &descriptorBufferInfos[i]; + writeInfos[i].descriptorSet = descriptorSets[i]; + writeInfos[i].descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + writeInfos[i].descriptorCount = 1; + + writeInfos[i].arrayElement = 0; + writeInfos[i].imageViewInfos = nullptr; + writeInfos[i].samplerInfos = nullptr; + writeInfos[i].tlasInfos = nullptr; + } + + result = palUpdateDescriptorSet(device, 3, writeInfos); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + // create pipeline layout + // even if the descriptor set layout is identical, pipeline layout creation needs + // a descriptor set layout per set (3) + PalDescriptorSetLayout* descriptorSetLayouts[3]; + descriptorSetLayouts[0] = descriptorSetLayout; + descriptorSetLayouts[1] = descriptorSetLayout; + descriptorSetLayouts[2] = descriptorSetLayout; + + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.descriptorSetLayoutCount = 3; + pipelineLayoutCreateInfo.descriptorSetLayouts = descriptorSetLayouts; + pipelineLayoutCreateInfo.usePushConstant = PAL_TRUE; + pipelineLayoutCreateInfo.pushConstantInfo.offset = 0; + pipelineLayoutCreateInfo.pushConstantInfo.size = sizeof(PushConstant); // must match shader + + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + // create a compute pipeline + PalComputePipelineCreateInfo pipelineCreateInfo = {0}; + pipelineCreateInfo.computeShader = shader; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + + result = palCreateComputePipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + palDestroyShader(shader); + + // create fence + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // record commands + result = palCmdBegin(cmdBuffer, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PushConstant pushConstant = {0}; + pushConstant.width = BUFFER_SIZE; + pushConstant.height = BUFFER_SIZE; + + // set 1 data (red) + pushConstant.set1Color[0] = 1.0f; + pushConstant.set1Color[1] = 0.0f; + pushConstant.set1Color[2] = 0.0f; + pushConstant.set1Color[3] = 1.0f; + + // set 2 data(green) + pushConstant.set2Color[0] = 0.0f; + pushConstant.set2Color[1] = 1.0f; + pushConstant.set2Color[2] = 0.0f; + pushConstant.set2Color[3] = 1.0f; + + // set 3 data (blue) + pushConstant.set3Color[0] = 0.0f; + pushConstant.set3Color[1] = 0.0f; + pushConstant.set3Color[2] = 1.0f; + pushConstant.set3Color[3] = 1.0f; + + palCmdBindPipeline(cmdBuffer, pipeline); + palCmdPushConstants(cmdBuffer, 0, sizeof(PushConstant), &pushConstant); + for (int i = 0; i < 3; i++) { + palCmdBindDescriptorSet(cmdBuffer, i, descriptorSets[i]); + } + + // we will use a helper function to calculate the number of group count + // we need on each axis. The workGroupSize on each axis must be less or equal + // to maxComputeWorkGroupInvocations from the adapter capabilities struct + PalWorkGroupBuildData buildData = {0}; + buildData.workCount[0] = BUFFER_SIZE; + buildData.workCount[1] = BUFFER_SIZE; + buildData.workCount[2] = 1; // no Z + + buildData.workGroupSize[0] = 16; // must match shader (local_size on glsl) + buildData.workGroupSize[1] = 16; // must match shader (local_size on glsl) + buildData.workGroupSize[2] = 1; // must match shader (local_size on glsl) + + // device limits + buildData.workGroupCount[0] = caps.computeCaps.maxWorkGroupCount[0]; + buildData.workGroupCount[1] = caps.computeCaps.maxWorkGroupCount[1]; + buildData.workGroupCount[2] = caps.computeCaps.maxWorkGroupCount[2]; + + uint32_t workGroupInfoCount = 0; + PalWorkGroupInfo* workGroupInfos = nullptr; + palBuildWorkGroupInfo(&buildData, &workGroupInfoCount, nullptr); + + workGroupInfos = palAllocate(nullptr, sizeof(PalWorkGroupInfo) * workGroupInfoCount, 0); + if (!workGroupInfos) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + palBuildWorkGroupInfo(&buildData, &workGroupInfoCount, workGroupInfos); + + // dispatch with the work group info + for (int i = 0; i < workGroupInfoCount; i++) { + // palDispatchBase is not supported on all platforms so we dont use it for this example + // We cap the buffer size small so we only get a single dispatch + // but you can add fields in yout constants to send the base to the shader driectly + uint32_t groupCountX = workGroupInfos[i].workGroupCount[0]; + uint32_t groupCountY = workGroupInfos[i].workGroupCount[1]; + uint32_t groupCountZ = workGroupInfos[i].workGroupCount[2]; + palCmdDispatch(cmdBuffer, groupCountX, groupCountY, groupCountZ); + } + palFree(nullptr, workGroupInfos); + + // set a barrier so we only read from the buffer after the shader has written to it + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_SHADER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_COMPUTE_SHADER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + + for (int i = 0; i < 3; i++) { + palCmdBufferBarrier(cmdBuffer, buffers[i], &barrierInfo); + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = bufferBytes; + palCmdCopyBuffer(cmdBuffer, stagingBuffers[i], buffers[i], ©Info); + } + + result = palCmdEnd(cmdBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit the command buffer to the GPU + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffer; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_TRANSFER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // wait for the fence + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + const char* names[3]; + names[0] = "set1_compute_output.ppm"; + names[1] = "set2_compute_output.ppm"; + names[2] = "set3_compute_output.ppm"; + + for (int i = 0; i < 3; i++) { + // now our staging buffer has the contents of the GPU buffer + // we map it and copy the contents to a ppm buffer and save it + void* ptr = nullptr; + result = palMapBuffer(stagingBuffers[i], 0, bufferBytes, &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + // write to a ppm output file + FILE* file = fopen(names[i], "wb"); + fprintf(file, "P6\n%d %d\n255\n", BUFFER_SIZE, BUFFER_SIZE); + float* pixels = (float*)ptr; + for (int y = 0; y < BUFFER_SIZE; y++) { + int row = BUFFER_SIZE - 1 - y; // flip y + for (int x = 0; x < BUFFER_SIZE; x++) { + int index = row * BUFFER_SIZE + x; + uint8_t rgb[3]; + + rgb[0] = pixels[index * 4 + 0] > 0.5f ? 255 : 0; + rgb[1] = pixels[index * 4 + 1] > 0.5f ? 255 : 0; + rgb[2] = pixels[index * 4 + 2] > 0.5f ? 255 : 0; + fwrite(rgb, 1, 3, file); + } + } + + fclose(file); + palUnmapBuffer(stagingBuffers[i]); + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + palFreeCommandBuffer(cmdBuffer); + palDestroyCommandPool(cmdPool); + + palDestroyDescriptorPool(descriptorPool); + palDestroyDescriptorSetLayout(descriptorSetLayout); + + for (int i = 0; i < 3; i++) { + palDestroyBuffer(buffers[i]); + palDestroyBuffer(stagingBuffers[i]); + } + + palDestroyFence(fence); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + return PAL_TRUE; +} diff --git a/tests/graphics/ray_tracing_test.c b/tests/graphics/ray_tracing_test.c new file mode 100644 index 00000000..3fbffc3a --- /dev/null +++ b/tests/graphics/ray_tracing_test.c @@ -0,0 +1,819 @@ + +#include "pal2/pal_graphics.h" +#include "tests.h" + +#define BUFFER_SIZE 400 +#define max(a, b) (a > b) ? a : b + +typedef struct { + float color[3]; // closest hit and miss color +} LocalData; + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool rayTracingTest() +{ + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalQueue* queue = nullptr; + PalCommandPool* cmdPool = nullptr; + PalCommandBuffer* cmdBuffer; + + PalShader* rayTracingShader = nullptr; + + PalBuffer* buffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + PalBuffer* vertexBuffer = nullptr; + PalBuffer* instanceBuffer = nullptr; + PalBuffer* blasBuffer = nullptr; + PalBuffer* tlasBuffer = nullptr; + PalBuffer* scratchBuffer = nullptr; + + PalDescriptorSetLayout* descriptorSetLayout = nullptr; + PalDescriptorPool* descriptorPool = nullptr; + PalDescriptorSet* descriptorSet = nullptr; + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalFence* fence = nullptr; + + PalShaderBindingTable* sbt = nullptr; + PalAccelerationStructure* blas = nullptr; + PalAccelerationStructure* tlas = nullptr; + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + PalResult result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + PalAdapterFeatures adapterFeatures = 0; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + + // Ray tracing is generally implemented on the graphics queue + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + adapterFeatures = palGetAdapterFeatures(adapter); + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_RAY_TRACING)) { + adapter = nullptr; + continue; + } + + if (!(adapterFeatures & PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS)) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.4 or dxil 6.3 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 4)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 3)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_RAY_TRACING; + features |= PAL_ADAPTER_FEATURE_BUFFER_DEVICE_ADDRESS; + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create a graphics command queue + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer(device, cmdPool, PAL_COMMAND_BUFFER_TYPE_PRIMARY, &cmdBuffer); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + + // create ray tracing shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* source = nullptr; + PalShaderEntryInfo entries[3]; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + source = "graphics/shaders/bin/spirv/ray_tracing.spv"; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + source = "graphics/shaders/bin/dxil/ray_tracing.dxil"; + } + + entries[0].stage = PAL_SHADER_STAGE_RAYGEN; + entries[0].entryName = "raygenMain"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_MISS; + entries[1].entryName = "missMain"; + entries[1].patchControlPoints = 0; + + entries[2].stage = PAL_SHADER_STAGE_CLOSEST_HIT; + entries[2].entryName = "closestHitMain"; + entries[2].patchControlPoints = 0; + + // read file + if (!readFile(source, nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(source, bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = entries; + shaderCreateInfo.entryCount = 3; + + result = palCreateShader(device, &shaderCreateInfo, &rayTracingShader); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + + uint32_t bufferBytes = BUFFER_SIZE * BUFFER_SIZE * sizeof(float) * 4; // must match shader + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = bufferBytes; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_STORAGE | PAL_BUFFER_USAGE_TRANSFER_SRC; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &buffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.size = bufferBytes; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_DST; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_READBACK; + + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // create a vertex buffer to store the vertices in + float vertices[] = {0.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f}; + + bufferCreateInfo.size = sizeof(vertices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_READ_ONLY_INPUT; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_DEVICE_ADDRESS; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + + result = palCreateBuffer(device, &bufferCreateInfo, &vertexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // copy vertices + void* data = nullptr; + result = palMapBuffer(vertexBuffer, 0, sizeof(vertices), &data); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + memcpy(data, vertices, sizeof(vertices)); + palUnmapBuffer(vertexBuffer); + + // fill BLAS and triangle geometry + PalDeviceAddress vertexBufferAddress = palGetBufferDeviceAddress(vertexBuffer); + PalGeometryDataTriangle triangle = {0}; + triangle.vertexBufferAddress = vertexBufferAddress; + triangle.vertexCount = 3; + triangle.vertexType = PAL_VERTEX_TYPE_FLOAT2; + triangle.vertexStride = sizeof(float) * 2; + + PalGeometry geometry = {0}; + geometry.data = ▵ + geometry.primitiveCount = 1; + geometry.type = PAL_GEOMETRY_TYPE_TRIANGLE; + + PalAccelerationStructureBuildInfo blasBuildInfo = {0}; + blasBuildInfo.type = PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + blasBuildInfo.count = 1; + blasBuildInfo.geometries = &geometry; + blasBuildInfo.buildHints = PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_BUILD; + blasBuildInfo.buildMode = PAL_ACCELERATION_STRUCTURE_BUILD_MODE_BUILD; + + // get the build sizes for blas + PalAccelerationStructureBuildSize buildSizes = {0}; + palGetAccelerationStructureBuildSize(device, &blasBuildInfo, &buildSizes); + + // create the blas buffer and blas + bufferCreateInfo.size = buildSizes.accelerationStructureSize; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_DEVICE_ADDRESS; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &blasBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + PalAccelerationStructureCreateInfo asCreateInfo = {0}; + asCreateInfo.type = PAL_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + asCreateInfo.buffer = blasBuffer; + asCreateInfo.size = buildSizes.accelerationStructureSize; + + result = palCreateAccelerationstructure(device, &asCreateInfo, &blas); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create acceleration structure"); + return PAL_FALSE; + } + + // create instance buffer + PalAccelerationStructureInstance asInstance = {0}; + asInstance.blas = blas; + asInstance.instanceId = 0; + asInstance.mask = 0xFF; + asInstance.hitGroupOffset = 0; // we only have 1 hitGroup + + float transform[12] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f}; + memcpy(asInstance.transform, transform, sizeof(float) * 12); + + uint64_t instanceBufferSize = 0; + palComputeInstanceStagingSize(device, 1, &instanceBufferSize); + + bufferCreateInfo.size = instanceBufferSize; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_READ_ONLY_INPUT; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_DEVICE_ADDRESS; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + + result = palCreateBuffer(device, &bufferCreateInfo, &instanceBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // copy instance struct to the buffer + data = nullptr; + result = palMapBuffer(instanceBuffer, 0, instanceBufferSize, &data); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + // we can not use a direct memcpy for instance buffers + palWriteInstanceStaging(device, 1, &asInstance, data); + palUnmapBuffer(instanceBuffer); + + // fill TLAS and instance geometry + PalDeviceAddress instanceBufferAddress = palGetBufferDeviceAddress(instanceBuffer); + PalAccelerationStructureBuildInfo tlasBuildInfo = {0}; + tlasBuildInfo.type = PAL_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + tlasBuildInfo.count = 1; + tlasBuildInfo.instanceBufferAddress = instanceBufferAddress; + tlasBuildInfo.buildHints = PAL_ACCELERATION_STRUCTURE_BUILD_HINT_FAST_BUILD; + tlasBuildInfo.buildMode = PAL_ACCELERATION_STRUCTURE_BUILD_MODE_BUILD; + + // get the build sizes for tlas + uint32_t blasScratchSize = (uint32_t)buildSizes.scratchBufferSize; + palGetAccelerationStructureBuildSize(device, &tlasBuildInfo, &buildSizes); + + // create the tlas buffer and tlas + bufferCreateInfo.size = buildSizes.accelerationStructureSize; + bufferCreateInfo.usages = PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_DEVICE_ADDRESS; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &tlasBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + asCreateInfo.type = PAL_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + asCreateInfo.buffer = tlasBuffer; + asCreateInfo.size = buildSizes.accelerationStructureSize; + + result = palCreateAccelerationstructure(device, &asCreateInfo, &tlas); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create acceleration structure"); + return PAL_FALSE; + } + + // create scratch buffer + bufferCreateInfo.size = max(buildSizes.scratchBufferSize, blasScratchSize); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_ACCELERATION_STRUCTURE_SCRATCH; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_DEVICE_ADDRESS; + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &scratchBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // create descriptor set layout + PalDescriptorSetLayoutBinding descriptorBindings[2]; + descriptorBindings[0].descriptorCount = 1; // not an array + descriptorBindings[0].descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + descriptorBindings[1].descriptorCount = 1; // not an array + descriptorBindings[1].descriptorType = PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE; + + PalDescriptorSetLayoutCreateInfo descriptorSetLayoutcreateInfo = {0}; + descriptorSetLayoutcreateInfo.bindingCount = 2; + descriptorSetLayoutcreateInfo.bindings = descriptorBindings; + + result = + palCreateDescriptorSetLayout(device, &descriptorSetLayoutcreateInfo, &descriptorSetLayout); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor set layout"); + return PAL_FALSE; + } + + // create descriptor pool + PalDescriptorPoolBindingSize bindingSizes[2]; + bindingSizes[0].bindingCount = 1; + bindingSizes[0].descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + + bindingSizes[1].bindingCount = 1; + bindingSizes[1].descriptorType = PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE; + + PalDescriptorPoolCreateInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.maxDescriptorSets = 1; // only one set + descriptorPoolCreateInfo.bindingSizeCount = 2; // two binding type + descriptorPoolCreateInfo.bindingSizes = bindingSizes; + + result = palCreateDescriptorPool(device, &descriptorPoolCreateInfo, &descriptorPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor pool"); + return PAL_FALSE; + } + + // allocate a single descriptor set from the descriptor pool + // using the layout we created above + result = palAllocateDescriptorSet(device, descriptorPool, descriptorSetLayout, &descriptorSet); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate descriptor set"); + return PAL_FALSE; + } + + // write the inital data to the descriptor set since its created empty + PalDescriptorBufferInfo descriptorStorageBufferInfo = {0}; + descriptorStorageBufferInfo.buffer = buffer; + descriptorStorageBufferInfo.offset = 0; + descriptorStorageBufferInfo.size = bufferBytes; + + PalDescriptorTLASInfo descriptorTlasInfo; + descriptorTlasInfo.tlas = tlas; + + PalDescriptorSetWriteInfo writeInfos[2]; + writeInfos[0].layoutBindingIndex = 0; + writeInfos[0].bufferInfos = &descriptorStorageBufferInfo; + writeInfos[0].descriptorSet = descriptorSet; + writeInfos[0].descriptorType = PAL_DESCRIPTOR_TYPE_STORAGE_BUFFER; + writeInfos[0].descriptorCount = 1; + writeInfos[0].arrayElement = 0; + writeInfos[0].imageViewInfos = nullptr; + writeInfos[0].tlasInfos = nullptr; + + writeInfos[1].layoutBindingIndex = 1; + writeInfos[1].tlasInfos = &descriptorTlasInfo; + writeInfos[1].descriptorSet = descriptorSet; + writeInfos[1].descriptorType = PAL_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE; + writeInfos[1].descriptorCount = 1; + writeInfos[1].arrayElement = 0; + writeInfos[1].imageViewInfos = nullptr; + writeInfos[1].bufferInfos = nullptr; + + result = palUpdateDescriptorSet(device, 2, writeInfos); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + // create pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.descriptorSetLayoutCount = 1; + pipelineLayoutCreateInfo.descriptorSetLayouts = &descriptorSetLayout; + + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + // the shader group array must respect the corect layout + // [raygen][miss][hitGroup][callable] + // [raygen][raygen][miss][hitGroup][hitGroup][callable] + PalRayTracingShaderGroupCreateInfo shaderGroupCreateInfos[3] = {0}; + + // raygen must be packed first always + shaderGroupCreateInfos[0].type = PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL; + shaderGroupCreateInfos[0].generalShaderIndex = 0; + shaderGroupCreateInfos[0].generalShaderEntryIndex = 0; + shaderGroupCreateInfos[0].anyHitShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[0].closestHitShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[0].intersectionShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[0].maxDataSize = 0; // no extra data + + // miss must be packed second always + shaderGroupCreateInfos[1].type = PAL_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL; + shaderGroupCreateInfos[1].generalShaderIndex = 0; + shaderGroupCreateInfos[1].generalShaderEntryIndex = 1; + shaderGroupCreateInfos[1].anyHitShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[1].closestHitShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[1].intersectionShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[1].maxDataSize = sizeof(LocalData); + + // hitGroup must be packed third always + shaderGroupCreateInfos[2].type = PAL_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT; + shaderGroupCreateInfos[2].closestHitShaderIndex = 0; + shaderGroupCreateInfos[2].closestHitShaderEntryIndex = 2; + shaderGroupCreateInfos[2].anyHitShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[2].generalShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[2].intersectionShaderIndex = PAL_UNUSED_SHADER_INDEX; + shaderGroupCreateInfos[2].maxDataSize = sizeof(LocalData); + + // create a ray tracing pipeline + PalRayTracingPipelineCreateInfo pipelineCreateInfo = {0}; + pipelineCreateInfo.maxRecursionDepth = 1; + pipelineCreateInfo.maxPayloadSize = 16; + pipelineCreateInfo.maxAttributeSize = 8; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.shaderCount = 1; + pipelineCreateInfo.shaderGroupCount = 3; + pipelineCreateInfo.shaderGroups = shaderGroupCreateInfos; + pipelineCreateInfo.shaders = &rayTracingShader; + + result = palCreateRayTracingPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + palDestroyShader(rayTracingShader); + + // create shader binding table + // we need 3 records, only closest hit (git group) only uses the local data + LocalData missLocalData; // black color for miss + missLocalData.color[0] = 0.0f; + missLocalData.color[1] = 0.0f; + missLocalData.color[2] = 0.0f; + + LocalData closestLocalData; // green color for miss + closestLocalData.color[0] = 0.0f; + closestLocalData.color[1] = 1.0f; + closestLocalData.color[2] = 0.0f; + + // the record array must respect the corect layout + // [raygen][miss][hitGroup][callable] + // [raygen][raygen][miss][hitGroup][hitGroup][callable] + + PalShaderBindingTableRecordInfo records[3]; + records[0].groupIndex = 0; // raygen group index + records[0].localDataSize = 0; + records[0].localData = nullptr; + + records[1].groupIndex = 1; // miss group index + records[1].localDataSize = sizeof(LocalData); + records[1].localData = &missLocalData; + + records[2].groupIndex = 2; // hit group index + records[2].localDataSize = sizeof(LocalData); + records[2].localData = &closestLocalData; + + PalShaderBindingTableCreateInfo sbtCreateInfo = {0}; + sbtCreateInfo.rayTracingPipeline = pipeline; + sbtCreateInfo.records = records; + sbtCreateInfo.recordCount = 3; + + result = palCreateShaderBindingTable(device, &sbtCreateInfo, &sbt); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader binding table"); + return PAL_FALSE; + } + + // create fence + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // record commands + result = palCmdBegin(cmdBuffer, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + palCmdBindPipeline(cmdBuffer, pipeline); + palCmdBindDescriptorSet(cmdBuffer, 0, descriptorSet); + + // build the blas and tlas infos + PalDeviceAddress scratchBufferAddress = palGetBufferDeviceAddress(scratchBuffer); + blasBuildInfo.dst = blas; + blasBuildInfo.scratchBufferAddress = scratchBufferAddress; + + tlasBuildInfo.dst = tlas; + tlasBuildInfo.scratchBufferAddress = scratchBufferAddress; + palCmdBuildAccelerationStructure(cmdBuffer, &blasBuildInfo); + + // make sure the BLAS builds before the TLAS. + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE; + barrierInfo.newState = PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD; + + palCmdAccelerationStructureBarrier(cmdBuffer, blas, &barrierInfo); + palCmdBuildAccelerationStructure(cmdBuffer, &tlasBuildInfo); + + barrierInfo.oldState = PAL_USAGE_STATE_ACCELERATION_STRUCTURE_WRITE; + barrierInfo.newState = PAL_USAGE_STATE_ACCELERATION_STRUCTURE_READ; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + palCmdAccelerationStructureBarrier(cmdBuffer, tlas, &barrierInfo); + palCmdTraceRays(cmdBuffer, sbt, 0, BUFFER_SIZE, BUFFER_SIZE, 1); + + // set a barrier so we only read from the buffer after the shader has written to it + barrierInfo.oldState = PAL_USAGE_STATE_SHADER_WRITE; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffer, buffer, &barrierInfo); + + // now we copy from the GPU buffer into the staging buffer + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = bufferBytes; + palCmdCopyBuffer(cmdBuffer, stagingBuffer, buffer, ©Info); + + result = palCmdEnd(cmdBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit the command buffer to the GPU + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffer; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // wait for the fence + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // now our staging buffer has the contents of the GPU buffer + // we map it and copy the contents to a ppm buffer and save it + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, bufferBytes, &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + // write to a ppm output file + FILE* file = fopen("ray_tracing_output.ppm", "wb"); + fprintf(file, "P6\n%d %d\n255\n", BUFFER_SIZE, BUFFER_SIZE); + float* pixels = (float*)ptr; + for (int y = 0; y < BUFFER_SIZE; y++) { + int row = BUFFER_SIZE - 1 - y; // flip y + for (int x = 0; x < BUFFER_SIZE; x++) { + int index = row * BUFFER_SIZE + x; + uint8_t rgb[3]; + + rgb[0] = pixels[index * 4 + 0] > 0.5f ? 255 : 0; + rgb[1] = pixels[index * 4 + 1] > 0.5f ? 255 : 0; + rgb[2] = pixels[index * 4 + 2] > 0.5f ? 255 : 0; + fwrite(rgb, 1, 3, file); + } + } + + // update sbt and trace again + // since we still have the record array we used to create the pipeline + // we just change the underlying data and update the record + + // change miss from black to white + missLocalData.color[0] = 1.0f; + missLocalData.color[1] = 1.0f; + missLocalData.color[2] = 1.0f; + + // change closest hit from green to red + closestLocalData.color[0] = 1.0f; + closestLocalData.color[1] = 0.0f; + closestLocalData.color[2] = 0.0f; + + PalShaderBindingTableRecordInfo updateRecords[2]; + updateRecords[0] = records[1]; // miss + updateRecords[1] = records[2]; // closest hit + + // update records + palUpdateShaderBindingTable(sbt, 2, updateRecords); + + // we dont need to rebuild the blas or tlas + // we just delete and create the fence again for simplicity + palDestroyFence(fence); + fence = nullptr; + + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // record commands + result = palCmdBegin(cmdBuffer, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + palCmdBindPipeline(cmdBuffer, pipeline); + palCmdBindDescriptorSet(cmdBuffer, 0, descriptorSet); + + // the previous trace transitioned the buffer to transfer read + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_SHADER_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + palCmdBufferBarrier(cmdBuffer, buffer, &barrierInfo); + palCmdTraceRays(cmdBuffer, sbt, 0, BUFFER_SIZE, BUFFER_SIZE, 1); + + // set a barrier to transition to transfer read so we can read from it after shader has + // written to it + barrierInfo.oldState = PAL_USAGE_STATE_SHADER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffer, buffer, &barrierInfo); + + // now we copy from the GPU buffer into the staging buffer + copyInfo.size = bufferBytes; + palCmdCopyBuffer(cmdBuffer, stagingBuffer, buffer, ©Info); + + result = palCmdEnd(cmdBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit the command buffer to the GPU + submitInfo.cmdBuffer = cmdBuffer; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_RAY_TRACING_SHADER; + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // wait for the fence + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // write to a ppm output file + file = fopen("ray_tracing_output2.ppm", "wb"); + fprintf(file, "P6\n%d %d\n255\n", BUFFER_SIZE, BUFFER_SIZE); + pixels = (float*)ptr; + for (int y = 0; y < BUFFER_SIZE; y++) { + int row = BUFFER_SIZE - 1 - y; // flip y + for (int x = 0; x < BUFFER_SIZE; x++) { + int index = row * BUFFER_SIZE + x; + uint8_t rgb[3]; + + rgb[0] = pixels[index * 4 + 0] > 0.5f ? 255 : 0; + rgb[1] = pixels[index * 4 + 1] > 0.5f ? 255 : 0; + rgb[2] = pixels[index * 4 + 2] > 0.5f ? 255 : 0; + fwrite(rgb, 1, 3, file); + } + } + + fclose(file); + palUnmapBuffer(stagingBuffer); + + palDestroyAccelerationStructure(blas); + palDestroyAccelerationStructure(tlas); + + palDestroyFence(fence); + palDestroyPipeline(pipeline); + palDestroyShaderBindingTable(sbt); + palDestroyPipelineLayout(pipelineLayout); + palDestroyDescriptorPool(descriptorPool); + palDestroyDescriptorSetLayout(descriptorSetLayout); + + palDestroyBuffer(buffer); + palDestroyBuffer(stagingBuffer); + palDestroyBuffer(vertexBuffer); + palDestroyBuffer(instanceBuffer); + palDestroyBuffer(blasBuffer); + palDestroyBuffer(tlasBuffer); + palDestroyBuffer(scratchBuffer); + + palFreeCommandBuffer(cmdBuffer); + palDestroyCommandPool(cmdPool); + palDestroyQueue(queue); + palDestroyDevice(device); + + palShutdownGraphics(); + return PAL_TRUE; +} diff --git a/tests/graphics/shaders/bin/dxil/compute.dxil b/tests/graphics/shaders/bin/dxil/compute.dxil new file mode 100644 index 00000000..f64b59bc Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/compute.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/compute_multi.dxil b/tests/graphics/shaders/bin/dxil/compute_multi.dxil new file mode 100644 index 00000000..f86b2ab8 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/compute_multi.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/descriptor_indexing.dxil b/tests/graphics/shaders/bin/dxil/descriptor_indexing.dxil new file mode 100644 index 00000000..8aa4e4ff Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/descriptor_indexing.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/geometry.dxil b/tests/graphics/shaders/bin/dxil/geometry.dxil new file mode 100644 index 00000000..8afc0732 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/geometry.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/geometry_vert.dxil b/tests/graphics/shaders/bin/dxil/geometry_vert.dxil new file mode 100644 index 00000000..931cb657 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/geometry_vert.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/mesh.dxil b/tests/graphics/shaders/bin/dxil/mesh.dxil new file mode 100644 index 00000000..3fbf1231 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/mesh.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/ray_tracing.dxil b/tests/graphics/shaders/bin/dxil/ray_tracing.dxil new file mode 100644 index 00000000..0dbd5ac7 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/ray_tracing.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/texture_frag.dxil b/tests/graphics/shaders/bin/dxil/texture_frag.dxil new file mode 100644 index 00000000..8f83ca90 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/texture_frag.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/texture_vert.dxil b/tests/graphics/shaders/bin/dxil/texture_vert.dxil new file mode 100644 index 00000000..fff537a1 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/texture_vert.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/triangle_frag.dxil b/tests/graphics/shaders/bin/dxil/triangle_frag.dxil new file mode 100644 index 00000000..6759c8c8 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/triangle_frag.dxil differ diff --git a/tests/graphics/shaders/bin/dxil/triangle_vert.dxil b/tests/graphics/shaders/bin/dxil/triangle_vert.dxil new file mode 100644 index 00000000..31548d57 Binary files /dev/null and b/tests/graphics/shaders/bin/dxil/triangle_vert.dxil differ diff --git a/tests/graphics/shaders/bin/spirv/compute.spv b/tests/graphics/shaders/bin/spirv/compute.spv new file mode 100644 index 00000000..82464732 Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/compute.spv differ diff --git a/tests/graphics/shaders/bin/spirv/compute_multi.spv b/tests/graphics/shaders/bin/spirv/compute_multi.spv new file mode 100644 index 00000000..7890a6ad Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/compute_multi.spv differ diff --git a/tests/graphics/shaders/bin/spirv/descriptor_indexing.spv b/tests/graphics/shaders/bin/spirv/descriptor_indexing.spv new file mode 100644 index 00000000..9dcfedbd Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/descriptor_indexing.spv differ diff --git a/tests/graphics/shaders/bin/spirv/geometry.spv b/tests/graphics/shaders/bin/spirv/geometry.spv new file mode 100644 index 00000000..60153d5e Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/geometry.spv differ diff --git a/tests/graphics/shaders/bin/spirv/geometry_vert.spv b/tests/graphics/shaders/bin/spirv/geometry_vert.spv new file mode 100644 index 00000000..5c7edbe9 Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/geometry_vert.spv differ diff --git a/tests/graphics/shaders/bin/spirv/mesh.spv b/tests/graphics/shaders/bin/spirv/mesh.spv new file mode 100644 index 00000000..0d24f9e4 Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/mesh.spv differ diff --git a/tests/graphics/shaders/bin/spirv/ray_tracing.spv b/tests/graphics/shaders/bin/spirv/ray_tracing.spv new file mode 100644 index 00000000..f40b2c0b Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/ray_tracing.spv differ diff --git a/tests/graphics/shaders/bin/spirv/texture_frag.spv b/tests/graphics/shaders/bin/spirv/texture_frag.spv new file mode 100644 index 00000000..05fc4aa9 Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/texture_frag.spv differ diff --git a/tests/graphics/shaders/bin/spirv/texture_vert.spv b/tests/graphics/shaders/bin/spirv/texture_vert.spv new file mode 100644 index 00000000..d4c5400a Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/texture_vert.spv differ diff --git a/tests/graphics/shaders/bin/spirv/triangle_frag.spv b/tests/graphics/shaders/bin/spirv/triangle_frag.spv new file mode 100644 index 00000000..4f44a9d7 Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/triangle_frag.spv differ diff --git a/tests/graphics/shaders/bin/spirv/triangle_vert.spv b/tests/graphics/shaders/bin/spirv/triangle_vert.spv new file mode 100644 index 00000000..9c114ffc Binary files /dev/null and b/tests/graphics/shaders/bin/spirv/triangle_vert.spv differ diff --git a/tests/graphics/shaders/src/compute.hlsl b/tests/graphics/shaders/src/compute.hlsl new file mode 100644 index 00000000..cebd6389 --- /dev/null +++ b/tests/graphics/shaders/src/compute.hlsl @@ -0,0 +1,30 @@ + + +struct PushData +{ + uint width; + uint height; + float4 color; +}; + +[[vk::push_constant]] +ConstantBuffer pc : register(b0); + +// We used raw Buffer but structured buffer can be used as well. +// PalDescriptorBufferInfo::stride must be set to 16 + +// binding 0 set 0 +[[vk::binding(0, 0)]] +RWByteAddressBuffer outBuffer : register(u0, space0); + +[numthreads(16, 16, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= pc.width || id.y >= pc.height) { + return; + } + + uint index = id.y * pc.width + id.x; + uint offset = index * 16; // sizeof(float4) + outBuffer.Store4(offset, asuint(pc.color)); +} diff --git a/tests/graphics/shaders/src/compute_multi.hlsl b/tests/graphics/shaders/src/compute_multi.hlsl new file mode 100644 index 00000000..ad50b3eb --- /dev/null +++ b/tests/graphics/shaders/src/compute_multi.hlsl @@ -0,0 +1,40 @@ + + +struct PushData +{ + uint width; + uint height; + float4 set1Color; + float4 set2Color; + float4 set3Color; +}; + +[[vk::push_constant]] +ConstantBuffer pc : register(b0); + +// we use structured buffer for this example. + +// binding 0 set 0 +[[vk::binding(0, 0)]] +RWStructuredBuffer set1OutBuffer : register(u0, space0); + +// binding 0 set 1 +[[vk::binding(0, 1)]] +RWStructuredBuffer set2OutBuffer : register(u0, space1); + +// binding 0 set 2 +[[vk::binding(0, 2)]] +RWStructuredBuffer set3OutBuffer : register(u0, space2); + +[numthreads(16, 16, 1)] +void main(uint3 id : SV_DispatchThreadID) +{ + if (id.x >= pc.width || id.y >= pc.height) { + return; + } + + uint index = id.y * pc.width + id.x; + set1OutBuffer[index] = pc.set1Color; + set2OutBuffer[index] = pc.set2Color; + set3OutBuffer[index] = pc.set3Color; +} diff --git a/tests/graphics/shaders/src/descriptor_indexing.hlsl b/tests/graphics/shaders/src/descriptor_indexing.hlsl new file mode 100644 index 00000000..bfbe2526 --- /dev/null +++ b/tests/graphics/shaders/src/descriptor_indexing.hlsl @@ -0,0 +1,47 @@ + + +struct PushData +{ + uint textureIndices[4]; +}; + +[[vk::push_constant]] +ConstantBuffer pc : register(b0); + +// binding 0 set 0 +[[vk::binding(0, 0)]] +Texture2D texs[] : register(t0, space0); + +// binding 1 set 0 +[[vk::binding(1, 0)]] +SamplerState samp : register(s0, space0); + +struct PSInput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD; +}; + +float4 main(PSInput input) : SV_Target +{ + int quadrant = 0; // quad is centered + if (input.pos.x < 320.0 && input.pos.y < 240.0) { + // red texture + quadrant = 0; + + } else if (input.pos.x >= 320.0 && input.pos.y < 240.0) { + // green texture + quadrant = 1; + + } else if (input.pos.x < 320.0 && input.pos.y >= 240.0) { + // blue texture + quadrant = 2; + + } else { + // yellow texture + quadrant = 3; + } + + uint index = pc.textureIndices[quadrant]; + return texs[NonUniformResourceIndex(index)].Sample(samp, input.uv); +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/geometry.hlsl b/tests/graphics/shaders/src/geometry.hlsl new file mode 100644 index 00000000..3889613e --- /dev/null +++ b/tests/graphics/shaders/src/geometry.hlsl @@ -0,0 +1,41 @@ + +struct PSInput +{ + float4 pos : SV_POSITION; +}; + +struct GSOutput +{ + float4 pos : SV_POSITION; + float4 color : COLOR; +}; + +[maxvertexcount(9)] +void main( + triangle PSInput inputs[3], + inout TriangleStream stream) +{ + float2 offsets[3] = { + float2(-0.6, 0.0 ), + float2( 0.0, 0.0 ), + float2( 0.6, 0.0 ) + }; + + float4 colors[3] = { + float4( 1.0, 0.0, 0.0, 1.0 ), + float4( 0.0, 1.0, 0.0, 1.0 ), + float4( 0.0, 0.0, 1.0, 1.0 ) + }; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + GSOutput output; + output.pos = inputs[j].pos; + output.pos.xy += offsets[i]; + output.color = colors[i]; + + stream.Append(output); + } + stream.RestartStrip(); + } +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/geometry_vert.hlsl b/tests/graphics/shaders/src/geometry_vert.hlsl new file mode 100644 index 00000000..783bb59c --- /dev/null +++ b/tests/graphics/shaders/src/geometry_vert.hlsl @@ -0,0 +1,18 @@ + +struct VSOutput +{ + float4 pos : SV_POSITION; +}; + +VSOutput main(uint vertexId : SV_VertexID) +{ + float2 positions[3] = { + float2( 0.0, 0.2), + float2( 0.2, -0.2 ), + float2(-0.2, -0.2 ) + }; + + VSOutput output; + output.pos = float4(positions[vertexId], 0.0, 1.0); + return output; +} diff --git a/tests/graphics/shaders/src/mesh.hlsl b/tests/graphics/shaders/src/mesh.hlsl new file mode 100644 index 00000000..5ca64819 --- /dev/null +++ b/tests/graphics/shaders/src/mesh.hlsl @@ -0,0 +1,27 @@ + +struct MSOutput +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; + +[outputtopology("triangle")] +[numthreads(1, 1, 1)] +void main( + out vertices MSOutput meshVertices[4], + out indices uint3 triangleIndices[2]) +{ + SetMeshOutputCounts(4, 2); + meshVertices[0].position = float4(-0.5, 0.5, 0.0, 1.0); + meshVertices[1].position = float4( 0.5, 0.5, 0.0, 1.0); + meshVertices[2].position = float4( 0.5,-0.5, 0.0, 1.0); + meshVertices[3].position = float4(-0.5,-0.5, 0.0, 1.0); + + meshVertices[0].color = float4(1.0, 0.0, 0.0, 1.0); + meshVertices[1].color = float4(0.0, 1.0, 0.0, 1.0); + meshVertices[2].color = float4(0.0, 0.0, 1.0, 1.0); + meshVertices[3].color = float4(1.0, 0.0, 0.0, 1.0); + + triangleIndices[0] = uint3(0, 1, 2); + triangleIndices[1] = uint3(0, 2, 3); +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/ray_tracing.hlsl b/tests/graphics/shaders/src/ray_tracing.hlsl new file mode 100644 index 00000000..f695d03a --- /dev/null +++ b/tests/graphics/shaders/src/ray_tracing.hlsl @@ -0,0 +1,65 @@ + + +struct RayPayload +{ + float3 color; +}; + +struct HitAttributes +{ + float2 unused; +}; + +[[vk::shader_record_ext]] +cbuffer RecordData : register(b0) +{ + float3 colorRecord; +}; + +// binding 0 set 0 +[[vk::binding(0, 0)]] +RWByteAddressBuffer outBuffer : register(u0, space0); + +// binding 1 set 0 +[[vk::binding(1, 0)]] +RaytracingAccelerationStructure tlas : register(t0, space0); + +[shader("raygeneration")] +void raygenMain() +{ + uint2 pixel = DispatchRaysIndex().xy; + uint2 size = DispatchRaysDimensions().xy; + RayPayload payload; + payload.color = float3(0.0, 0.0, 0.0); + + float2 uv = (float2(pixel) + 0.5) / float2(size); + float2 ndcUv = uv * 2.0 - 1.0; + + RayDesc desc; + desc.Origin = float3(0.0, 0.0, -3.0); + desc.Direction = normalize(float3(ndcUv.x, ndcUv.y, 1.0)); + desc.TMin = 0.001; + desc.TMax = 1000.0; + TraceRay(tlas, RAY_FLAG_NONE, 0xFF, 0, 0, 0, desc, payload); + + if (pixel.x >= size.x || pixel.y >= size.y) { + return; + } + + uint index = pixel.y * size.x + pixel.x; + uint offset = index * 16; // sizeof(float4) + float4 color = float4(payload.color, 1.0); + outBuffer.Store4(offset, asuint(color)); +} + +[shader("miss")] +void missMain(inout RayPayload payload) +{ + payload.color = colorRecord; +} + +[shader("closesthit")] +void closestHitMain(inout RayPayload payload, in HitAttributes attr) +{ + payload.color = colorRecord; +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/texture_frag.hlsl b/tests/graphics/shaders/src/texture_frag.hlsl new file mode 100644 index 00000000..ad0ef885 --- /dev/null +++ b/tests/graphics/shaders/src/texture_frag.hlsl @@ -0,0 +1,19 @@ + +// binding 0 set 0 +[[vk::binding(0, 0)]] +Texture2D tex : register(t0, space0); + +// binding 1 set 0 +[[vk::binding(1, 0)]] +SamplerState samp : register(s0, space0); + +struct PSInput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD; +}; + +float4 main(PSInput input) : SV_Target +{ + return tex.Sample(samp, input.uv); +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/texture_vert.hlsl b/tests/graphics/shaders/src/texture_vert.hlsl new file mode 100644 index 00000000..ff9ef8cc --- /dev/null +++ b/tests/graphics/shaders/src/texture_vert.hlsl @@ -0,0 +1,20 @@ + +struct VSInput +{ + float2 pos : POSITION; + float2 uv : TEXCOORD; +}; + +struct VSOutput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD; +}; + +VSOutput main(VSInput input) +{ + VSOutput output; + output.pos = float4(input.pos, 0.0, 1.0); + output.uv = input.uv; + return output; +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/triangle_frag.hlsl b/tests/graphics/shaders/src/triangle_frag.hlsl new file mode 100644 index 00000000..756feece --- /dev/null +++ b/tests/graphics/shaders/src/triangle_frag.hlsl @@ -0,0 +1,11 @@ + +struct PSInput +{ + float4 pos : SV_POSITION; + float4 color : COLOR; +}; + +float4 main(PSInput input) : SV_Target +{ + return input.color; +} \ No newline at end of file diff --git a/tests/graphics/shaders/src/triangle_vert.hlsl b/tests/graphics/shaders/src/triangle_vert.hlsl new file mode 100644 index 00000000..eb111619 --- /dev/null +++ b/tests/graphics/shaders/src/triangle_vert.hlsl @@ -0,0 +1,20 @@ + +struct VSInput +{ + float2 pos : POSITION; + float3 color : COLOR; +}; + +struct VSOutput +{ + float4 pos : SV_POSITION; + float4 color : COLOR; +}; + +VSOutput main(VSInput input) +{ + VSOutput output; + output.pos = float4(input.pos, 0.0, 1.0); + output.color = float4(input.color, 1.0); + return output; +} diff --git a/tests/graphics/texture_test.c b/tests/graphics/texture_test.c new file mode 100644 index 00000000..7d3ac75d --- /dev/null +++ b/tests/graphics/texture_test.c @@ -0,0 +1,1040 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 +#define TEXTURE_WIDTH 128 +#define TEXTURE_HEIGHT 128 +#define CHECKER_SIZE 16 + +static void createCheckerboardTexture( + uint32_t* texture, + uint32_t width, + uint32_t height, + uint32_t checkerSize) +{ + uint8_t* pixels = (uint8_t*)texture; + for (int32_t y = 0; y < height; ++y) { + for (int32_t x = 0; x < width; ++x) { + int32_t i = (y * width + x) * 4; + int checker = ((x / checkerSize) ^ (y / checkerSize)) & 1; + if (checker) { + pixels[i + 0] = 255; // Red bit + pixels[i + 1] = 0; // Green bit + pixels[i + 2] = 0; // Blue bit + pixels[i + 3] = 255; // Alpha bit + + } else { + pixels[i + 0] = 0; // Red bit + pixels[i + 1] = 255; // Green bit + pixels[i + 2] = 0; // Blue bit + pixels[i + 3] = 255; // Alpha bit + } + } + } +} + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool textureTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[2]; + + PalBuffer* vertexBuffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + + PalDescriptorSetLayout* descriptorSetLayout = nullptr; + PalDescriptorPool* descriptorPool = nullptr; + PalDescriptorSet* descriptorSet = nullptr; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Texture Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures adapterFeatures = palGetAdapterFeatures(adapter); + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create vertex and staging buffer + // clang-format off + float vertices[] = { + -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, 0.5f, 1.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + + -0.5f, 0.5f, 0.0f, 0.0f, + 0.5f, -0.5f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 1.0f}; + // clang-format on + + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = sizeof(vertices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_VERTEX; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &vertexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; // will send + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // map the staging buffer and upload the vertices + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, sizeof(vertices), &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + memcpy(ptr, vertices, sizeof(vertices)); + palUnmapBuffer(stagingBuffer); + + PalFence* fence = nullptr; + result = palCreateFence(device, PAL_FALSE, &fence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // we dont want to load the texture from disk so we will create a + // checkerboard texture and use that rather + uint32_t texture[TEXTURE_WIDTH * TEXTURE_HEIGHT]; + memset(texture, 0, TEXTURE_WIDTH * TEXTURE_HEIGHT); + createCheckerboardTexture(texture, TEXTURE_WIDTH, TEXTURE_HEIGHT, CHECKER_SIZE); + + // create image for the texture + PalImage* checkerboard = nullptr; + PalImageCreateInfo imageCreateInfo = {0}; + imageCreateInfo.arrayLayerCount = 1; + imageCreateInfo.depth = 1; + imageCreateInfo.format = PAL_FORMAT_R8G8B8A8_UNORM; + imageCreateInfo.mipLevelCount = 1; // simple + imageCreateInfo.sampleCount = PAL_SAMPLE_COUNT_1; // simple + imageCreateInfo.type = PAL_IMAGE_TYPE_2D; + imageCreateInfo.usages = PAL_IMAGE_USAGE_TRANSFER_DST | PAL_IMAGE_USAGE_SAMPLED; + imageCreateInfo.width = TEXTURE_WIDTH; + imageCreateInfo.height = TEXTURE_HEIGHT; + imageCreateInfo.memoryUsage = PAL_IMAGE_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateImage(device, &imageCreateInfo, &checkerboard); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image"); + return PAL_FALSE; + } + + // create staging buffer to transfer the data to the image + PalBufferImageCopyInfo bufferImageCopyInfo = {0}; + bufferImageCopyInfo.ImageArrayLayerCount = 1; + bufferImageCopyInfo.imageWidth = TEXTURE_WIDTH; + bufferImageCopyInfo.imageHeight = TEXTURE_HEIGHT; + bufferImageCopyInfo.imageDepth = 1; // 2D image + + PalImageStagingRequirements stagingReq = {0}; + palComputeImageStagingRequirements( + device, + imageCreateInfo.format, + &bufferImageCopyInfo, + &stagingReq); + + // update our copy with the required buffer row length and buffer image height + bufferImageCopyInfo.bufferRowLength = stagingReq.bufferRowLength; + bufferImageCopyInfo.bufferImageHeight = stagingReq.bufferImageHeight; + + // create staging buffer to transfer the data to the image + PalBuffer* imageStagingBuffer = nullptr; + PalBufferCreateInfo imageStagingBufferCreateInfo = {0}; + imageStagingBufferCreateInfo.size = stagingReq.bufferSize; + imageStagingBufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; + imageStagingBufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + + result = palCreateBuffer(device, &imageStagingBufferCreateInfo, &imageStagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // copy data + void* data = nullptr; + result = palMapBuffer(imageStagingBuffer, 0, imageStagingBufferCreateInfo.size, &data); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + palWriteImageStaging(device, imageCreateInfo.format, &bufferImageCopyInfo, texture, data); + + palUnmapBuffer(imageStagingBuffer); + + // use the first command buffer to upload the copy + // and reset it when done + // set a fence and check at the last line before the main loop + // to see if we have to wait for the copy to be executed + result = palCmdBegin(cmdBuffers[0], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = sizeof(vertices); + palCmdCopyBuffer(cmdBuffers[0], vertexBuffer, stagingBuffer, ©Info); + + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffers[0], vertexBuffer, &barrierInfo); + + // copy image staging buffer to the checkerboard image + // first the image must be in the correct layout + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + + // set a barrier on the image to transition it into transfer dst state + PalImageSubresourceRange checkerboardRange = {0}; + checkerboardRange.startMipLevel = 0; + checkerboardRange.startArrayLayer = 0; + checkerboardRange.mipLevelCount = 1; + checkerboardRange.layerArrayCount = 1; + + palCmdImageBarrier(cmdBuffers[0], checkerboard, &checkerboardRange, &barrierInfo); + palCmdCopyBufferToImage(cmdBuffers[0], checkerboard, imageStagingBuffer, &bufferImageCopyInfo); + + // transition the image to shader read state + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_SHADER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_FRAGMENT_SHADER; + palCmdImageBarrier(cmdBuffers[0], checkerboard, &checkerboardRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[0]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[0]; + submitInfo.fence = fence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_TRANSFER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // now we have the checkerboard texture data in the image + // we need an image view and a sampler + PalImageView* checkerboardImageView = nullptr; + PalImageViewCreateInfo checkerboardImageViewCreateInfo = {0}; + checkerboardImageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + checkerboardImageViewCreateInfo.subresourceRange = checkerboardRange; + checkerboardImageViewCreateInfo.format = PAL_FORMAT_R8G8B8A8_UNORM; + + result = palCreateImageView( + device, + checkerboard, + &checkerboardImageViewCreateInfo, + &checkerboardImageView); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + PalSampler* sampler = nullptr; + PalSamplerCreateInfo samplerCreateInfo = {0}; + samplerCreateInfo.addressModeU = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeV = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.addressModeW = PAL_SAMPLER_ADDRESS_MODE_REPEAT; + samplerCreateInfo.borderColor = PAL_BORDER_COLOR_INT_OPAQUE_BLACK; + samplerCreateInfo.compareOp = PAL_COMPARE_OP_NEVER; // will not be used if its not enabled + + samplerCreateInfo.enableAnisotropy = PAL_FALSE; + samplerCreateInfo.enableCompare = PAL_FALSE; + samplerCreateInfo.magFilterMode = PAL_FILTER_MODE_LINEAR; + samplerCreateInfo.minFilterMode = PAL_FILTER_MODE_LINEAR; + samplerCreateInfo.maxAnisotropy = 1.0f; + + result = palCreateSampler(device, &samplerCreateInfo, &sampler); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create sampler"); + return PAL_FALSE; + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[2]; + PalShaderEntryInfo entries[2]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/texture_vert.spv"; + sources[1] = "graphics/shaders/bin/spirv/texture_frag.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/texture_vert.dxil"; + sources[1] = "graphics/shaders/bin/dxil/texture_frag.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_VERTEX; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + for (int i = 0; i < 2; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create descriptor set layout + PalDescriptorSetLayoutBinding descriptorBindings[2]; + descriptorBindings[0].descriptorCount = 1; // not an array + descriptorBindings[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + descriptorBindings[1].descriptorCount = 1; // not an array + descriptorBindings[1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + + PalDescriptorSetLayoutCreateInfo descriptorSetLayoutcreateInfo = {0}; + descriptorSetLayoutcreateInfo.bindingCount = 2; + descriptorSetLayoutcreateInfo.bindings = descriptorBindings; + + result = + palCreateDescriptorSetLayout(device, &descriptorSetLayoutcreateInfo, &descriptorSetLayout); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor set layout"); + return PAL_FALSE; + } + + // create descriptor pool + PalDescriptorPoolBindingSize storageBufferBindingsizes[2]; + storageBufferBindingsizes[0].bindingCount = 1; + storageBufferBindingsizes[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + + storageBufferBindingsizes[1].bindingCount = 1; + storageBufferBindingsizes[1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + + PalDescriptorPoolCreateInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.maxDescriptorSets = 1; // only one set + descriptorPoolCreateInfo.bindingSizeCount = 2; + descriptorPoolCreateInfo.bindingSizes = storageBufferBindingsizes; + + result = palCreateDescriptorPool(device, &descriptorPoolCreateInfo, &descriptorPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create descriptor pool"); + return PAL_FALSE; + } + + // allocate a single descriptor set from the descriptor pool + // using the layout we created above + result = palAllocateDescriptorSet(device, descriptorPool, descriptorSetLayout, &descriptorSet); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate descriptor set"); + return PAL_FALSE; + } + + // write the inital data to the descriptor set since its created empty + PalDescriptorImageViewInfo descriptorImageInfo = {0}; + descriptorImageInfo.imageView = checkerboardImageView; + + PalDescriptorSamplerInfo descriptorSamplerInfo = {0}; + descriptorSamplerInfo.sampler = sampler; + + PalDescriptorSetWriteInfo writeInfos[2]; + writeInfos[0].layoutBindingIndex = 0; + writeInfos[0].imageViewInfos = &descriptorImageInfo; + writeInfos[0].descriptorSet = descriptorSet; + writeInfos[0].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + writeInfos[0].descriptorCount = 1; + + writeInfos[0].arrayElement = 0; + writeInfos[0].bufferInfos = nullptr; + writeInfos[0].samplerInfos = nullptr; + writeInfos[0].tlasInfos = nullptr; + + writeInfos[1].layoutBindingIndex = 1; + writeInfos[1].samplerInfos = &descriptorSamplerInfo; + writeInfos[1].descriptorSet = descriptorSet; + writeInfos[1].descriptorType = PAL_DESCRIPTOR_TYPE_SAMPLER; + writeInfos[1].descriptorCount = 1; + + writeInfos[1].arrayElement = 0; + writeInfos[1].imageViewInfos = nullptr; + writeInfos[1].tlasInfos = nullptr; + writeInfos[1].bufferInfos = nullptr; + + result = palUpdateDescriptorSet(device, 2, writeInfos); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to update descriptor set"); + return PAL_FALSE; + } + + // create pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.descriptorSetLayoutCount = 1; + pipelineLayoutCreateInfo.descriptorSetLayouts = &descriptorSetLayout; + + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + PalVertexLayout vertexLayout = {0}; + PalVertexAttribute vertexAttributes[2]; + + // position + vertexAttributes[0].semanticID = PAL_VERTEX_SEMANTIC_ID_POSITION; + vertexAttributes[0].type = PAL_VERTEX_TYPE_FLOAT2; + + // texture coordinates + vertexAttributes[1].semanticID = PAL_VERTEX_SEMANTIC_ID_TEXCOORD; + vertexAttributes[1].type = PAL_VERTEX_TYPE_FLOAT2; + + vertexLayout.attributeCount = 2; + vertexLayout.attributes = vertexAttributes; + vertexLayout.binding = 0; // first vertex buffer binding slot + vertexLayout.type = PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX; + + pipelineCreateInfo.vertexLayoutCount = 1; + pipelineCreateInfo.vertexLayouts = &vertexLayout; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 2; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 2; i++) { + palDestroyShader(shaders[i]); + } + + // wait for the vertices copy to be done + result = palWaitFence(fence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // the vertices have been copied + palDestroyFence(fence); + palDestroyBuffer(stagingBuffer); + palDestroyBuffer(imageStagingBuffer); + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdBindDescriptorSet(cmdBuffers[currentFrame], 0, descriptorSet); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + + uint64_t offset[] = {0}; + palCmdBindVertexBuffers(cmdBuffers[currentFrame], 0, 1, &vertexBuffer, offset); + palCmdDraw(cmdBuffers[currentFrame], 6, 1, 0, 0); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyDescriptorPool(descriptorPool); + palDestroyDescriptorSetLayout(descriptorSetLayout); + + palDestroySampler(sampler); + palDestroyImageView(checkerboardImageView); + palDestroyImage(checkerboard); + palDestroyBuffer(vertexBuffer); + + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/graphics/triangle_test.c b/tests/graphics/triangle_test.c new file mode 100644 index 00000000..77f9f52d --- /dev/null +++ b/tests/graphics/triangle_test.c @@ -0,0 +1,774 @@ + +#include "pal2/pal_graphics.h" +#include "pal2/pal_system.h" +#include "pal2/pal_video.h" +#include "tests.h" + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 +#define MAX_FRAMES_IN_FLIGHT 2 + +static void PAL_CALL onGraphicsDebug( + void* userData, + PalDebugMessageSeverity severity, + PalDebugMessageType type, + const char* msg) +{ + palLog(nullptr, msg); +} + +PalBool triangleTest() +{ + PalResult result; + PalWindow* window = nullptr; + PalEventDriver* eventDriver = nullptr; + + PalAdapter* adapter = nullptr; + PalDevice* device = nullptr; + PalSurface* surface = nullptr; + PalQueue* queue = nullptr; + PalSwapchain* swapchain = nullptr; + PalCommandPool* cmdPool = nullptr; + PalImageView** imageViews = nullptr; + + PalCommandBuffer* cmdBuffers[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore* imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; + PalFence* inFlightFences[MAX_FRAMES_IN_FLIGHT]; + PalSemaphore** renderFinishedSemaphores; // count of swapchain images + PalFence** inFlightImages; // count of swapchain images + + PalPipelineLayout* pipelineLayout = nullptr; + PalPipeline* pipeline = nullptr; + PalShader* shaders[2]; + + PalBuffer* vertexBuffer = nullptr; + PalBuffer* stagingBuffer = nullptr; + + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create event driver"); + return PAL_FALSE; + } + + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + + result = palInitVideo(nullptr, eventDriver, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + PalWindowCreateInfo windowCreateInfo = {0}; + windowCreateInfo.height = WINDOW_HEIGHT; + windowCreateInfo.width = WINDOW_WIDTH; + windowCreateInfo.show = PAL_TRUE; + windowCreateInfo.title = "Triangle Window"; + + PalVideoFeatures videoFeatures = palGetVideoFeatures(); + if (!(videoFeatures & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { + windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; + } + + result = palCreateWindow(&windowCreateInfo, &window); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create window"); + return PAL_FALSE; + } + + // get window handle. You can use any window from any library + // so long as you can get the window handle and display (if on X11, wayland) + // If pal video system will not be used, there is no need to initialize it + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + + // using pal_system.h will be easy to know the underlying windowing API or use typedefs. + // We will use the pal_system module. + PalPlatformInfo platformInfo = {0}; + palGetPlatformInfo(&platformInfo); + + PalWindowInstanceType windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_XCB; + if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WAYLAND) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WAYLAND; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_X11) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_X11; + + } else if (platformInfo.apiType == PAL_PLATFORM_API_TYPE_WIN32) { + windowInstanceType = PAL_WINDOW_INSTANCE_TYPE_WIN32; + } + + PalGraphicsDebugger debugger = {0}; + debugger.callback = onGraphicsDebug; + debugger.userData = nullptr; + + result = palInitGraphics(nullptr, nullptr, 0, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize graphics"); + return PAL_FALSE; + } + + // enumerate all available adapters + uint32_t adapterCount = 0; + result = palEnumerateAdapters(&adapterCount, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + if (adapterCount == 0) { + palLog(nullptr, "No adapters found"); + return PAL_FALSE; + } + palLog(nullptr, "Adapter count: %d", adapterCount); + + PalAdapter** adapters = nullptr; + adapters = palAllocate(nullptr, sizeof(PalAdapter*) * adapterCount, 0); + if (!adapters) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + result = palEnumerateAdapters(&adapterCount, adapters); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get adapters"); + return PAL_FALSE; + } + + PalAdapterCapabilities caps = {0}; + PalAdapterInfo adapterInfo = {0}; + for (int32_t i = 0; i < adapterCount; i++) { + adapter = adapters[i]; + palGetAdapterCapabilities(adapter, &caps); + if (caps.maxGraphicsQueues == 0) { + adapter = nullptr; + continue; + } + + // We want an adapter that supports spirv 1.0 or dxil 6.0 + palGetAdapterInfo(adapter, &adapterInfo); + + // we prefer spirv first if an adapter supports multiple shader formats + uint32_t target = 0; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_SPIRV); + if (target >= PAL_MAKE_SHADER_TARGET(1, 0)) { + break; + } + } + + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + target = palGetHighestSupportedShaderTarget(adapter, PAL_SHADER_FORMAT_DXIL); + if (target >= PAL_MAKE_SHADER_TARGET(6, 0)) { + break; + } + } + + adapter = nullptr; + continue; + } + + palFree(nullptr, adapters); + if (!adapter) { + palLog(nullptr, "Failed to find a required adapter"); + return PAL_FALSE; + } + + // create a device + PalAdapterFeatures adapterFeatures = palGetAdapterFeatures(adapter); + PalAdapterFeatures features = PAL_ADAPTER_FEATURE_SWAPCHAIN; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + features |= PAL_ADAPTER_FEATURE_FENCE_RESET; + } + + result = palCreateDevice(adapter, features, &device); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create device"); + return PAL_FALSE; + } + + // create surface + result = palCreateSurface( + device, + winHandle.nativeWindow, + winHandle.nativeInstance, + windowInstanceType, + &surface); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create surface"); + return PAL_FALSE; + } + + // create a graphics command queue and check if its supports presenting to the surface + PalBool foundQueue = PAL_FALSE; + for (int i = 0; i < caps.maxGraphicsQueues; i++) { + result = palCreateQueue(device, PAL_QUEUE_TYPE_GRAPHICS, &queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create queue"); + return PAL_FALSE; + } + + if (!palCanQueuePresent(queue, surface)) { + palDestroyQueue(queue); + queue = nullptr; + } else { + // found a queue + foundQueue = PAL_TRUE; + break; + } + } + + if (!foundQueue) { + palLog(nullptr, "Failed to find a queue that can present to the surface"); + return PAL_FALSE; + } + + // create a swapchain with the graphics queue + PalSurfaceCapabilities surfaceCaps = {0}; + palGetSurfaceCapabilities(device, surface, &surfaceCaps); + + PalSwapchainCreateInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.clipped = PAL_TRUE; + swapchainCreateInfo.compositeAlpha = PAL_COMPOSITE_ALPHA_OPAQUE; + swapchainCreateInfo.height = WINDOW_HEIGHT; + swapchainCreateInfo.width = WINDOW_WIDTH; + swapchainCreateInfo.imageArrayLayerCount = 1; + swapchainCreateInfo.presentMode = PAL_PRESENT_MODE_FIFO; + swapchainCreateInfo.format = PAL_SURFACE_FORMAT_BGRA8_UNORM_SRGB_NONLINEAR; + + // rare but possible on andriod + if (WINDOW_WIDTH > surfaceCaps.maxImageWidth) { + swapchainCreateInfo.width = surfaceCaps.maxImageWidth / 2; + } + + if (WINDOW_HEIGHT > surfaceCaps.maxImageHeight) { + swapchainCreateInfo.height = surfaceCaps.maxImageHeight / 2; + } + + // check if the minimal image count is not good for you + // and increase it but not pass the max count + swapchainCreateInfo.imageCount = surfaceCaps.minImageCount; + if (swapchainCreateInfo.imageCount == 1) { + swapchainCreateInfo.imageCount++; + if (surfaceCaps.maxImageCount < 2) { + palLog(nullptr, "Surface does not support double buffers"); + return PAL_FALSE; + } + } + + result = palCreateSwapchain(device, queue, surface, &swapchainCreateInfo, &swapchain); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create swapchain"); + return PAL_FALSE; + } + + // get all swapchain images and create image views for them + uint32_t imageCount = swapchainCreateInfo.imageCount; + imageViews = palAllocate(nullptr, sizeof(PalImageView*) * imageCount, 0); + inFlightImages = palAllocate(nullptr, sizeof(PalFence*) * imageCount, 0); + renderFinishedSemaphores = palAllocate(nullptr, sizeof(PalSemaphore*) * imageCount, 0); + if (!imageViews || !inFlightImages || !renderFinishedSemaphores) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + PalImageInfo imageInfo; + palGetImageInfo(palGetSwapchainImage(swapchain, 0), &imageInfo); + + PalImageViewCreateInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.type = PAL_IMAGE_VIEW_TYPE_2D; + imageViewCreateInfo.subresourceRange.layerArrayCount = 1; + imageViewCreateInfo.subresourceRange.mipLevelCount = 1; + imageViewCreateInfo.subresourceRange.startArrayLayer = 0; + imageViewCreateInfo.subresourceRange.startMipLevel = 0; + imageViewCreateInfo.format = imageInfo.format; + + for (int i = 0; i < imageCount; i++) { + // get swapchain image + // this is fast since the images are cache by PAL + PalImage* image = palGetSwapchainImage(swapchain, i); + if (!image) { + palLog(nullptr, "Failed to get swapchain image"); + } + + result = palCreateImageView(device, image, &imageViewCreateInfo, &imageViews[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create image view"); + return PAL_FALSE; + } + + // create render finished semaphores + result = palCreateSemaphore(device, PAL_FALSE, &renderFinishedSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + inFlightImages[i] = nullptr; + } + + result = palCreateCommandPool(device, queue, &cmdPool); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create command pool"); + return PAL_FALSE; + } + + // create synchronization objects and command buffers + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + result = palCreateSemaphore(device, PAL_FALSE, &imageAvailableSemaphores[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create semaphore"); + return PAL_FALSE; + } + + result = palCreateFence(device, PAL_TRUE, &inFlightFences[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + result = palAllocateCommandBuffer( + device, + cmdPool, + PAL_COMMAND_BUFFER_TYPE_PRIMARY, + &cmdBuffers[i]); + + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to allocate command buffer"); + return PAL_FALSE; + } + } + + // create vertex and staging buffer + // clang-format off + float vertices[] = { + 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 0.0f, 1.0f + }; + // clang-format on + + PalBufferCreateInfo bufferCreateInfo = {0}; + bufferCreateInfo.size = sizeof(vertices); + bufferCreateInfo.usages = PAL_BUFFER_USAGE_VERTEX; + bufferCreateInfo.usages |= PAL_BUFFER_USAGE_TRANSFER_DST; // will recieve + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_GPU_ONLY; + + result = palCreateBuffer(device, &bufferCreateInfo, &vertexBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + bufferCreateInfo.usages = PAL_BUFFER_USAGE_TRANSFER_SRC; // will send + bufferCreateInfo.memoryUsage = PAL_BUFFER_MEMORY_USAGE_AUTO_CPU_UPLOAD; + result = palCreateBuffer(device, &bufferCreateInfo, &stagingBuffer); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create buffer"); + return PAL_FALSE; + } + + // map the staging buffer and upload the vertices + void* ptr = nullptr; + result = palMapBuffer(stagingBuffer, 0, sizeof(vertices), &ptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to map buffer"); + return PAL_FALSE; + } + + memcpy(ptr, vertices, sizeof(vertices)); + palUnmapBuffer(stagingBuffer); + + PalFence* tmpFence = nullptr; + result = palCreateFence(device, PAL_FALSE, &tmpFence); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create fence"); + return PAL_FALSE; + } + + // use the first command buffer to upload the copy + // and reset it when done + // set a fence and check at the last line before the main loop + // to see if we have to wait for the copy to be executed + result = palCmdBegin(cmdBuffers[0], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + PalBufferCopyInfo copyInfo = {0}; + copyInfo.size = sizeof(vertices); + palCmdCopyBuffer(cmdBuffers[0], vertexBuffer, stagingBuffer, ©Info); + + PalBarrierInfo barrierInfo = {0}; + barrierInfo.oldState = PAL_USAGE_STATE_TRANSFER_WRITE; + barrierInfo.srcStages = PAL_PIPELINE_STAGE_TRANSFER; + barrierInfo.newState = PAL_USAGE_STATE_TRANSFER_READ; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_TRANSFER; + palCmdBufferBarrier(cmdBuffers[0], vertexBuffer, &barrierInfo); + + result = palCmdEnd(cmdBuffers[0]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[0]; + submitInfo.fence = tmpFence; + submitInfo.waitStages = PAL_PIPELINE_STAGE_TRANSFER; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // create shaders + uint32_t bytecodeSize = 0; + void* bytecode = nullptr; + const char* sources[2]; + PalShaderEntryInfo entries[2]; + + PalViewport viewport = {0}; + viewport.width = (float)WINDOW_WIDTH; + viewport.maxDepth = 1.0f; + + PalShaderCreateInfo shaderCreateInfo = {0}; + if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_SPIRV) { + sources[0] = "graphics/shaders/bin/spirv/triangle_vert.spv"; + sources[1] = "graphics/shaders/bin/spirv/triangle_frag.spv"; + + viewport.height = -(float)WINDOW_HEIGHT; + viewport.y = (float)WINDOW_HEIGHT; + + } else if (adapterInfo.shaderFormats & PAL_SHADER_FORMAT_DXIL) { + sources[0] = "graphics/shaders/bin/dxil/triangle_vert.dxil"; + sources[1] = "graphics/shaders/bin/dxil/triangle_frag.dxil"; + + viewport.height = (float)WINDOW_HEIGHT; + } + + entries[0].stage = PAL_SHADER_STAGE_VERTEX; + entries[0].entryName = "main"; + entries[0].patchControlPoints = 0; + + entries[1].stage = PAL_SHADER_STAGE_FRAGMENT; + entries[1].entryName = "main"; + entries[1].patchControlPoints = 0; + + for (int i = 0; i < 2; i++) { + // read file + if (!readFile(sources[i], nullptr, &bytecodeSize)) { + palLog(nullptr, "Failed to read shader file"); + return PAL_FALSE; + } + + bytecode = palAllocate(nullptr, bytecodeSize, 0); + if (!bytecode) { + palLog(nullptr, "Failed to allocate memory"); + return PAL_FALSE; + } + + readFile(sources[i], bytecode, &bytecodeSize); + + shaderCreateInfo.code = bytecode; + shaderCreateInfo.codeSize = bytecodeSize; + shaderCreateInfo.entries = &entries[i]; + shaderCreateInfo.entryCount = 1; + + result = palCreateShader(device, &shaderCreateInfo, &shaders[i]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create shader"); + return PAL_FALSE; + } + + palFree(nullptr, bytecode); + } + + // create a pipeline layout + PalPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {0}; + result = palCreatePipelineLayout(device, &pipelineLayoutCreateInfo, &pipelineLayout); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline layout"); + return PAL_FALSE; + } + + PalRenderingLayoutInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.colorAttachentCount = 1; + renderingLayoutInfo.colorAttachmentsFormat = &imageInfo.format; + renderingLayoutInfo.sampleCount = PAL_SAMPLE_COUNT_1; + renderingLayoutInfo.viewCount = 1; + + // create graphics pipeline + PalGraphicsPipelineCreateInfo pipelineCreateInfo = {0}; + PalVertexLayout vertexLayout = {0}; + PalVertexAttribute vertexAttributes[2]; + + // position + vertexAttributes[0].semanticID = PAL_VERTEX_SEMANTIC_ID_POSITION; + vertexAttributes[0].type = PAL_VERTEX_TYPE_FLOAT2; + + // color + vertexAttributes[1].semanticID = PAL_VERTEX_SEMANTIC_ID_COLOR; + vertexAttributes[1].type = PAL_VERTEX_TYPE_FLOAT3; + + vertexLayout.attributeCount = 2; + vertexLayout.attributes = vertexAttributes; + vertexLayout.binding = 0; // first vertex buffer binding slot + vertexLayout.type = PAL_VERTEX_LAYOUT_TYPE_PER_VERTEX; + + pipelineCreateInfo.vertexLayoutCount = 1; + pipelineCreateInfo.vertexLayouts = &vertexLayout; + + // color blend attachment + PalColorBlendAttachment blendAttachment = {0}; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_RED; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_GREEN; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_BLUE; + blendAttachment.colorWriteMask |= PAL_COLOR_MASK_ALPHA; + + pipelineCreateInfo.colorBlendAttachments = &blendAttachment; + pipelineCreateInfo.colorBlendAttachmentCount = 1; + + // shaders + pipelineCreateInfo.shaderCount = 2; + pipelineCreateInfo.shaders = shaders; + + pipelineCreateInfo.topology = PAL_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + pipelineCreateInfo.pipelineLayout = pipelineLayout; + pipelineCreateInfo.renderingLayout = &renderingLayoutInfo; + + result = palCreateGraphicsPipeline(device, &pipelineCreateInfo, &pipeline); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to create pipeline"); + return PAL_FALSE; + } + + for (int i = 0; i < 2; i++) { + palDestroyShader(shaders[i]); + } + + // wait for the vertices copy to be done + result = palWaitFence(tmpFence, PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // the vertices have been copied + palDestroyFence(tmpFence); + palDestroyBuffer(stagingBuffer); + + // main loop + uint32_t currentFrame = 0; + PalBool running = PAL_TRUE; + + PalRect2D scissor = {0}; + scissor.height = WINDOW_HEIGHT; + scissor.width = WINDOW_WIDTH; + + while (running) { + // update the video system to push video events + palUpdateVideo(); + + PalEvent event; + while (palPollEvent(eventDriver, &event)) { + switch (event.type) { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; + break; + } + + case PAL_EVENT_TYPE_KEYDOWN: { + PalKeycode keycode = 0; + palUnpackUint32(event.data, &keycode, nullptr); + if (keycode == PAL_KEYCODE_ESCAPE) { + running = PAL_FALSE; + } + break; + } + } + } + + result = palWaitFence(inFlightFences[currentFrame], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + // get next swapchain image + PalSwapchainNextImageInfo nextImageInfo = {0}; + nextImageInfo.fence = nullptr; + nextImageInfo.signalSemaphore = imageAvailableSemaphores[currentFrame]; + nextImageInfo.timeout = PAL_INFINITE; + + uint32_t imageIndex = 0; + result = palGetNextSwapchainImage(swapchain, &nextImageInfo, &imageIndex); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to get next swapchain image"); + return PAL_FALSE; + } + + if (inFlightImages[imageIndex] != nullptr) { + result = palWaitFence(inFlightImages[imageIndex], PAL_INFINITE); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + inFlightImages[imageIndex] = inFlightFences[currentFrame]; + if (adapterFeatures & PAL_ADAPTER_FEATURE_FENCE_RESET) { + result = palResetFence(inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + + } else { + // recreate since we dont support fence resetting + palDestroyFence(inFlightFences[currentFrame]); + + result = palCreateFence(device, PAL_FALSE, &inFlightFences[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait fence"); + return PAL_FALSE; + } + } + + // reset the command buffer + result = palResetCommandBuffer(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to reset command buffer"); + return PAL_FALSE; + } + + result = palCmdBegin(cmdBuffers[currentFrame], nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to begin command buffer"); + return PAL_FALSE; + } + + // change the state of the image view to make it renderable + barrierInfo.oldState = PAL_USAGE_STATE_UNDEFINED; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + barrierInfo.newState = PAL_USAGE_STATE_COLOR_ATTACHMENT_WRITE; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + PalImageSubresourceRange imageRange = {0}; + imageRange.layerArrayCount = 1; + imageRange.mipLevelCount = 1; + imageRange.startArrayLayer = 0; + imageRange.startMipLevel = 0; + + PalImage* image = palGetSwapchainImage(swapchain, imageIndex); + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + PalClearValue clearValue; + clearValue.color[0] = 0.2f; + clearValue.color[1] = 0.2f; + clearValue.color[2] = 0.2f; + clearValue.color[3] = 1.0f; + + PalAttachmentDesc colorAttachment = {0}; + colorAttachment.loadOp = PAL_LOAD_OP_CLEAR; + colorAttachment.storeOp = PAL_STORE_OP_STORE; + colorAttachment.clearValue = clearValue; + colorAttachment.imageView = imageViews[imageIndex]; + + PalRenderingInfo renderingInfo = {0}; + renderingInfo.viewCount = 1; + renderingInfo.colorAttachentCount = 1; + renderingInfo.colorAttachments = &colorAttachment; + renderingInfo.arrayLayerCount = 1; + renderingInfo.viewCount = 1; + renderingInfo.renderArea.width = WINDOW_WIDTH; + renderingInfo.renderArea.height = WINDOW_HEIGHT; + + palCmdBeginRendering(cmdBuffers[currentFrame], &renderingInfo); + palCmdBindPipeline(cmdBuffers[currentFrame], pipeline); + palCmdSetViewport(cmdBuffers[currentFrame], 1, &viewport); + palCmdSetScissors(cmdBuffers[currentFrame], 1, &scissor); + + uint64_t offset[] = {0}; + palCmdBindVertexBuffers(cmdBuffers[currentFrame], 0, 1, &vertexBuffer, offset); + palCmdDraw(cmdBuffers[currentFrame], 3, 1, 0, 0); + palCmdEndRendering(cmdBuffers[currentFrame]); + + // change the state of the image view to make it presentable + barrierInfo.oldState = barrierInfo.newState; + barrierInfo.srcStages = barrierInfo.dstStages; + barrierInfo.newState = PAL_USAGE_STATE_PRESENT; + barrierInfo.dstStages = PAL_PIPELINE_STAGE_NONE; + palCmdImageBarrier(cmdBuffers[currentFrame], image, &imageRange, &barrierInfo); + + result = palCmdEnd(cmdBuffers[currentFrame]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to end command buffer"); + return PAL_FALSE; + } + + // submit command buffer + PalCommandBufferSubmitInfo submitInfo = {0}; + submitInfo.cmdBuffer = cmdBuffers[currentFrame]; + submitInfo.fence = inFlightFences[currentFrame]; + submitInfo.waitSemaphore = imageAvailableSemaphores[currentFrame]; + submitInfo.signalSemaphore = renderFinishedSemaphores[imageIndex]; + submitInfo.waitStages = PAL_PIPELINE_STAGE_COLOR_ATTACHMENT; + + result = palSubmitCommandBuffer(queue, &submitInfo); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to submit command buffer"); + return PAL_FALSE; + } + + // present + result = palPresentSwapchain(swapchain, imageIndex, renderFinishedSemaphores[imageIndex]); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to present swapchain"); + return PAL_FALSE; + } + + currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; + } + + result = palWaitQueue(queue); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to wait queue"); + return PAL_FALSE; + } + + palDestroyPipeline(pipeline); + palDestroyPipelineLayout(pipelineLayout); + + for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { + palDestroySemaphore(imageAvailableSemaphores[i]); + palDestroyFence(inFlightFences[i]); + palFreeCommandBuffer(cmdBuffers[i]); + } + + for (int i = 0; i < imageCount; i++) { + palDestroySemaphore(renderFinishedSemaphores[i]); + palDestroyImageView(imageViews[i]); + } + + palDestroyBuffer(vertexBuffer); + palDestroyCommandPool(cmdPool); + palDestroySwapchain(swapchain); + palDestroySurface(surface); + palDestroyQueue(queue); + palDestroyDevice(device); + palShutdownGraphics(); + + palFree(nullptr, imageViews); + palFree(nullptr, renderFinishedSemaphores); + palFree(nullptr, inFlightImages); + + palDestroyWindow(window); + palShutdownVideo(); + palDestroyEventDriver(eventDriver); + return PAL_TRUE; +} diff --git a/tests/multi_thread_opengl_test.c b/tests/opengl/multi_thread_opengl_test.c similarity index 60% rename from tests/multi_thread_opengl_test.c rename to tests/opengl/multi_thread_opengl_test.c index 09501564..f7f5e52e 100644 --- a/tests/multi_thread_opengl_test.c +++ b/tests/opengl/multi_thread_opengl_test.c @@ -1,7 +1,7 @@ -#include "pal/pal_opengl.h" -#include "pal/pal_thread.h" -#include "pal/pal_video.h" +#include "pal2/pal_opengl.h" +#include "pal2/pal_thread.h" +#include "pal2/pal_video.h" #include "tests.h" // opengl typedefs @@ -11,8 +11,7 @@ typedef void(PAL_GL_APIENTRY* PFNGLCLEARCOLORPROC)( float blue, float alpha); -typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)( - Uint32 mask); // use GL typedefs if needed +typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)(uint32_t mask); // use GL typedefs if needed typedef void (*glFlushFn)(); typedef void (*glBeginFn)(unsigned int); @@ -35,9 +34,9 @@ typedef void (*glViewportFn)( int); typedef struct { - bool driverCreated; - bool running; - bool fixedPipeline; + PalBool driverCreated; + PalBool running; + PalBool fixedPipeline; PalEventDriver* videoEventDriver; PalEventDriver* openglEventDriver; PalGLContext* context; @@ -46,7 +45,6 @@ typedef struct { static void* PAL_CALL eventDriverWorker(void* arg) { - PalResult result; SharedState* shared = (SharedState*)arg; if (!shared) { palLog(nullptr, "Failed to get thread arg"); @@ -54,51 +52,48 @@ static void* PAL_CALL eventDriverWorker(void* arg) } PalEventDriverCreateInfo createInfo = {0}; - result = palCreateEventDriver(&createInfo, &shared->videoEventDriver); + PalResult result = palCreateEventDriver(&createInfo, &shared->videoEventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); + logResult(result, "Failed to create event driver"); return nullptr; } // create the opengl driver as well result = palCreateEventDriver(&createInfo, &shared->openglEventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); + logResult(result, "Failed to create event driver"); return nullptr; } // set dispatch modes. opengl needs only window resize palSetEventDispatchMode( shared->openglEventDriver, - PAL_EVENT_WINDOW_SIZE, - PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_WINDOW_SIZE, + PAL_DISPATCH_MODE_POLL); // video needs window close and resize palSetEventDispatchMode( shared->videoEventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_WINDOW_CLOSE, + PAL_DISPATCH_MODE_POLL); palSetEventDispatchMode( shared->videoEventDriver, - PAL_EVENT_WINDOW_SIZE, - PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_WINDOW_SIZE, + PAL_DISPATCH_MODE_POLL); palSetEventDispatchMode( shared->videoEventDriver, - PAL_EVENT_KEYDOWN, - PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_KEYDOWN, + PAL_DISPATCH_MODE_POLL); // we are done - shared->driverCreated = true; + shared->driverCreated = PAL_TRUE; return nullptr; } static void* PAL_CALL rendererWorkder(void* arg) { - PalResult result; SharedState* shared = (SharedState*)arg; if (!shared) { palLog(nullptr, "Failed to get thread arg"); @@ -106,11 +101,10 @@ static void* PAL_CALL rendererWorkder(void* arg) } // make the context current on the renderer thread - result = palMakeContextCurrent(&shared->window, shared->context); + PalResult result = palMakeContextCurrent(&shared->window, shared->context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to make opengl context current: %s", error); - return nullptr; + logResult(result, "Failed to make opengl context current"); + return PAL_FALSE; } // load function procs @@ -124,16 +118,16 @@ static void* PAL_CALL rendererWorkder(void* arg) glGetErrorFn glGetError; glViewportFn glViewport; - glClearColor = (PFNGLCLEARCOLORPROC)palGLGetProcAddress("glClearColor"); - glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); - glBegin = (glBeginFn)palGLGetProcAddress("glBegin"); - glEnd = (glEndFn)palGLGetProcAddress("glEnd"); - glVertex2f = (glVertex2fFn)palGLGetProcAddress("glVertex2f"); - glColor3f = (glColor3fFn)palGLGetProcAddress("glColor3f"); - glFlush = (glFlushFn)palGLGetProcAddress("glFlush"); + glClearColor = (PFNGLCLEARCOLORPROC)palGetGLProcAddress("glClearColor"); + glClear = (PFNGLCLEARPROC)palGetGLProcAddress("glClear"); + glBegin = (glBeginFn)palGetGLProcAddress("glBegin"); + glEnd = (glEndFn)palGetGLProcAddress("glEnd"); + glVertex2f = (glVertex2fFn)palGetGLProcAddress("glVertex2f"); + glColor3f = (glColor3fFn)palGetGLProcAddress("glColor3f"); + glFlush = (glFlushFn)palGetGLProcAddress("glFlush"); - glGetError = (glGetErrorFn)palGLGetProcAddress("glGetError"); - glViewport = (glViewportFn)palGLGetProcAddress("glViewport"); + glGetError = (glGetErrorFn)palGetGLProcAddress("glGetError"); + glViewport = (glViewportFn)palGetGLProcAddress("glViewport"); // set clear color glViewport(0, 0, 640, 480); @@ -144,12 +138,12 @@ static void* PAL_CALL rendererWorkder(void* arg) PalEvent event; while (palPollEvent(shared->openglEventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_SIZE: { - Uint32 width, height; + case PAL_EVENT_TYPE_WINDOW_SIZE: { + uint32_t width, height; palUnpackUint32(event.data, &width, &height); palLog( nullptr, - "Video driver sent a resize event (%d, %d)", + "Video event driver sent a resize event (%d, %d)", width, height); @@ -183,8 +177,7 @@ static void* PAL_CALL rendererWorkder(void* arg) // swap buffers result = palSwapBuffers(&shared->window, shared->context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to swap buffers from: %s", error); + logResult(result, "Failed to swap buffers"); return nullptr; } } @@ -192,41 +185,39 @@ static void* PAL_CALL rendererWorkder(void* arg) return nullptr; } -bool multiThreadOpenGlTest() +PalBool multiThreadOpenGlTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Multi Thread OpenGL Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalThread* eventDriverThread = nullptr; SharedState* shared = nullptr; shared = palAllocate(nullptr, sizeof(SharedState), 0); if (!shared) { palLog(nullptr, "Failed to allocate shared state"); - return false; + return PAL_FALSE; } + memset(shared, 0, sizeof(SharedState)); // create a thread that creates two event drivers PalThreadCreateInfo threadCreateInfo = {0}; threadCreateInfo.entry = eventDriverWorker; threadCreateInfo.arg = (void*)shared; - result = palCreateThread(&threadCreateInfo, &eventDriverThread); + + PalThread* eventDriverThread = nullptr; + PalResult result = palCreateThread(&threadCreateInfo, &eventDriverThread); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } // check to see if the event driver thread is done creating the drivers // if not we wait for it if (!shared->driverCreated) { // this will be detached automatically when done - palJoinThread(eventDriverThread, nullptr); + result = palJoinThread(eventDriverThread, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to join thread"); + return PAL_FALSE; + } } else { palDetachThread(eventDriverThread); // we dont need it anymore @@ -235,53 +226,60 @@ bool multiThreadOpenGlTest() // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, shared->videoEventDriver); + result = palInitVideo(nullptr, shared->videoEventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } - // get the instance or display handle and pass it to the opengl system - // This must be called before the opengl system is initialized - palGLSetInstance(palGetInstance()); + // check if opengl API is supported or fallback to opengl es + PalGLAPI openglAPI; + void* videoInstance = palGetInstance(); + const PalBool* supportedAPIs = palGetSupportedGLAPIs(videoInstance); + if (supportedAPIs) { + if (supportedAPIs[PAL_GL_API_OPENGL]) { + openglAPI = PAL_GL_API_OPENGL; - // initialize video and opengl systems - // we need to initialize these on the main thread - result = palInitGL(nullptr); + } else { + openglAPI = PAL_GL_API_OPENGL_ES; + } + + } else { + palLog(nullptr, "Failed to get supported opengl apis"); + return PAL_FALSE; + } + + // initialize the opengl system. This loads the icd. + result = palInitGL(openglAPI, videoInstance, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; + logResult(result, "Failed to initialize opengl"); + return PAL_FALSE; } // get all FBConfigs and select one - Int32 fbCount = 0; - result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); + uint32_t fbCount = 0; + result = palEnumerateGLFBConfigs(&fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } if (fbCount == 0) { palLog(nullptr, "No supported FBConfig found"); - return false; + return PAL_FALSE; } PalGLFBConfig* fbConfigs = nullptr; fbConfigs = palAllocate(nullptr, sizeof(PalGLFBConfig) * fbCount, 0); if (!fbConfigs) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } - result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); + result = palEnumerateGLFBConfigs(&fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } // we get our desired FBConfig @@ -294,74 +292,49 @@ bool multiThreadOpenGlTest() desired.depthBits = 24; desired.stencilBits = 8; desired.samples = 2; - desired.stereo = false; // not widely supported - desired.sRGB = true; - desired.doubleBuffer = true; + desired.stereo = PAL_FALSE; // not widely supported + desired.sRGB = PAL_TRUE; + desired.doubleBuffer = PAL_TRUE; const PalGLFBConfig* closest = nullptr; closest = palGetClosestGLFBConfig(fbConfigs, fbCount, &desired); - // tell the video system to use our closest FBConfig - // to create the windows - result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set FBConfig: %s", error); - return false; - } - - // if not using pal_opengl with pal_video - // we get the backend string from the opengl system and - // get the backend from it - // Possible values are `wgl`, `glx`, `gles`, `egl`. - // PalFBConfigBackend backend; - // const char* glBackendString = palGLGetBackend(); - // if (strcmp(glBackendString, "wgl") == 0) { - // backend = PAL_CONFIG_BACKEND_WGL; - - // } else if (strcmp(glBackendString, "glx") == 0) { - // backend = PAL_CONFIG_BACKEND_GLX; - - // } else if (strcmp(glBackendString, "gles") == 0) { - // backend = PAL_CONFIG_BACKEND_GLES; - - // } else if (strcmp(glBackendString, "egl") == 0) { - // backend = PAL_CONFIG_BACKEND_EGL; - // } - // all windows also needs to be created on the main thread - PalWindow* window = nullptr; PalWindowCreateInfo windowCreateInfo = {0}; windowCreateInfo.width = 640; windowCreateInfo.height = 480; - windowCreateInfo.show = true; + windowCreateInfo.show = PAL_TRUE; windowCreateInfo.style = PAL_WINDOW_STYLE_RESIZABLE; windowCreateInfo.title = "Multi Thread OpenGL Window"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves windowCreateInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } + // set the backend and the fbConfig. We use PAL_FBCONFIG_BACKEND_PAL_OPENGL + // because we are using both pal_video and pal_opengl + windowCreateInfo.fbConfigBackend = PAL_FBCONFIG_BACKEND_PAL_OPENGL; + windowCreateInfo.fbConfigIndex = closest->index; + + PalWindow* window = nullptr; result = palCreateWindow(&windowCreateInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // GL context needs to be created on the main thread const PalGLInfo* glInfo = palGetGLInfo(); // get the native handles of our created window - PalWindowHandleInfoEx winHandle = {0}; - winHandle = palGetWindowHandleInfoEx(window); - - shared->window.display = winHandle.nativeDisplay; + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); + shared->window.instance = winHandle.nativeInstance; // On Wayland the window is the wl_egl_window if (winHandle.nativeHandle3) { // the window has a valid wl_egl_window @@ -372,7 +345,7 @@ bool multiThreadOpenGlTest() } PalGLContextCreateInfo contextCreateInfo = {0}; - contextCreateInfo.debug = true; + contextCreateInfo.debug = PAL_TRUE; contextCreateInfo.fbConfig = closest; contextCreateInfo.major = glInfo->major; contextCreateInfo.minor = glInfo->minor; @@ -381,10 +354,10 @@ bool multiThreadOpenGlTest() // we dont want to get into GL pipeline for this example // so we request a Compatibility profile if supported // NOTE: is its not supported, no triangle would be displayed - shared->fixedPipeline = false; + shared->fixedPipeline = PAL_FALSE; if (glInfo->extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { contextCreateInfo.profile = PAL_GL_PROFILE_COMPATIBILITY; - shared->fixedPipeline = true; + shared->fixedPipeline = PAL_TRUE; } if (!shared->fixedPipeline) { @@ -393,13 +366,11 @@ bool multiThreadOpenGlTest() result = palCreateGLContext(&contextCreateInfo, &shared->context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create opengl context: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to create opengl context"); + return PAL_FALSE; } - shared->running = true; + shared->running = PAL_TRUE; // we create a renderer thread for opengl // we dont wait for the renderer thread since it has its own while loop @@ -410,9 +381,8 @@ bool multiThreadOpenGlTest() threadCreateInfo.arg = (void*)shared; result = palCreateThread(&threadCreateInfo, &rendererThread); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } // we run the video while loop here @@ -423,21 +393,21 @@ bool multiThreadOpenGlTest() PalEvent event; while (palPollEvent(shared->videoEventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - shared->running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + shared->running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - shared->running = false; + shared->running = PAL_FALSE; } break; } - case PAL_EVENT_WINDOW_SIZE: { + case PAL_EVENT_TYPE_WINDOW_SIZE: { // tell the opengl driver about our size change palPushEvent(shared->openglEventDriver, &event); break; @@ -448,7 +418,11 @@ bool multiThreadOpenGlTest() // we wait for the render thread to finish with // the current frame and destroy the context - palJoinThread(rendererThread, nullptr); + result = palJoinThread(rendererThread, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to join thread"); + return PAL_FALSE; + } palDestroyGLContext(shared->context); palShutdownGL(); @@ -464,5 +438,5 @@ bool multiThreadOpenGlTest() palDestroyEventDriver(shared->openglEventDriver); palFree(nullptr, fbConfigs); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/opengl_context_test.c b/tests/opengl/opengl_context_test.c similarity index 58% rename from tests/opengl_context_test.c rename to tests/opengl/opengl_context_test.c index 5a1491f7..dba0dab9 100644 --- a/tests/opengl_context_test.c +++ b/tests/opengl/opengl_context_test.c @@ -1,6 +1,6 @@ -#include "pal/pal_opengl.h" -#include "pal/pal_video.h" // for window +#include "pal2/pal_opengl.h" +#include "pal2/pal_video.h" // for window #include "tests.h" static const char* g_BoolsToSting[2] = {"False", "True"}; @@ -12,95 +12,86 @@ typedef void(PAL_GL_APIENTRY* PFNGLCLEARCOLORPROC)( float blue, float alpha); -typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)( - Uint32 mask); // use GL typedefs if needed +typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)(uint32_t mask); // use GL typedefs if needed -bool openglContextTest() +PalBool openglContextTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Opengl Context Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = nullptr; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } - // get the instance or display handle and pass it to the opengl system - // This must be called before the opengl system is initialized - palGLSetInstance(palGetInstance()); + // check if opengl API is supported or fallback to opengl es + PalGLAPI openglAPI; + void* videoInstance = palGetInstance(); + const PalBool* supportedAPIs = palGetSupportedGLAPIs(videoInstance); + if (supportedAPIs) { + if (supportedAPIs[PAL_GL_API_OPENGL]) { + openglAPI = PAL_GL_API_OPENGL; - // initialize the opengl system - result = palInitGL(nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; + } else { + openglAPI = PAL_GL_API_OPENGL_ES; + } + + } else { + palLog(nullptr, "Failed to get supported opengl apis"); + return PAL_FALSE; } - PalWindow* window = nullptr; - PalGLContext* context = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount = 0; - bool running = false; + // initialize the opengl system. This loads the icd. + result = palInitGL(openglAPI, videoInstance, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize opengl"); + return PAL_FALSE; + } // enumerate supported opengl framebuffer configs - // glWindow must be nullptr - result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); + uint32_t fbCount = 0; + result = palEnumerateGLFBConfigs(&fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } palLog(nullptr, "GL FBConfig count: %d", fbCount); if (fbCount == 0) { palLog(nullptr, "No supported FBConfig found"); - return false; + return PAL_FALSE; } PalGLFBConfig* fbConfigs = nullptr; fbConfigs = palAllocate(nullptr, sizeof(PalGLFBConfig) * fbCount, 0); if (!fbConfigs) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // enumerate supported opengl framebuffer configs - // glWindow must be nullptr - result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); + result = palEnumerateGLFBConfigs(&fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } // we desire a FB config and see what is closest the driver will give us @@ -114,9 +105,9 @@ bool openglContextTest() desired.stencilBits = 8; desired.samples = 2; - desired.stereo = false; // not widely supported - desired.sRGB = true; - desired.doubleBuffer = true; + desired.stereo = PAL_FALSE; // not widely supported + desired.sRGB = PAL_TRUE; + desired.doubleBuffer = PAL_TRUE; // get the closest const PalGLFBConfig* closest = nullptr; @@ -138,80 +129,48 @@ bool openglContextTest() palLog(nullptr, " sRGB: %s", g_BoolsToSting[closest->sRGB]); palLog(nullptr, ""); - // set the FBConfig that will be used by PAL video system - // to create windows. this must be set before creating a window - // for this example, we set the closest we desired. - // If pal_opengl and pal_video will be used together, - // then its recommended to use PAL_CONFIG_BACKEND_PAL_OPENGL - - // NOTE: If PAL video system will not be used, - // users need to call the direct OS call to achieve this. - result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set GL FBConfig: %s", error); - return false; - } - - // if not using pal_opengl with pal_video - // we get the backend string from the opengl system and - // get the backend from it - // Possible values are `wgl`, `glx`, `gles`, `egl`. - // PalFBConfigBackend backend; - // const char* glBackendString = palGLGetBackend(); - // if (strcmp(glBackendString, "wgl") == 0) { - // backend = PAL_CONFIG_BACKEND_WGL; - - // } else if (strcmp(glBackendString, "glx") == 0) { - // backend = PAL_CONFIG_BACKEND_GLX; - - // } else if (strcmp(glBackendString, "gles") == 0) { - // backend = PAL_CONFIG_BACKEND_GLES; - - // } else if (strcmp(glBackendString, "egl") == 0) { - // backend = PAL_CONFIG_BACKEND_EGL; - // } - + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Opengl Context Window"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } + // set the backend and the fbConfig. We use PAL_FBCONFIG_BACKEND_PAL_OPENGL + // because we are using both pal_video and pal_opengl + createInfo.fbConfigBackend = PAL_FBCONFIG_BACKEND_PAL_OPENGL; + createInfo.fbConfigIndex = closest->index; + // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // get window handle. You can use any window from any library // so long as you can get the window handle and display (if on X11, wayland) // If pal video system will not be used, there is no need to initialize it - PalWindowHandleInfoEx winHandle = {0}; - winHandle = palGetWindowHandleInfoEx(window); + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); // PalGLWindow is just a struct to hold native handles PalGLWindow glWindow = {0}; - glWindow.display = winHandle.nativeDisplay; + glWindow.instance = winHandle.nativeInstance; // On Wayland the window is the wl_egl_window if (winHandle.nativeHandle3) { @@ -226,11 +185,12 @@ bool openglContextTest() const PalGLInfo* info = palGetGLInfo(); // fill the context create info with the closest FBConfig - contextCreateInfo.debug = true; // debug context + PalGLContextCreateInfo contextCreateInfo = {0}; + contextCreateInfo.debug = PAL_TRUE; // debug context contextCreateInfo.fbConfig = closest; // we use the closest to what we want contextCreateInfo.major = info->major; // context major contextCreateInfo.minor = info->minor; // context minor - contextCreateInfo.noError = false; // check PAL_GL_EXTENSION_NO_ERROR + contextCreateInfo.noError = PAL_FALSE; // check PAL_GL_EXTENSION_NO_ERROR // check PAL_GL_EXTENSION_FLUSH_CONTROL contextCreateInfo.release = PAL_GL_RELEASE_BEHAVIOR_NONE; @@ -242,7 +202,7 @@ bool openglContextTest() contextCreateInfo.window = &glWindow; if (info->extensions & PAL_GL_EXTENSION_CREATE_CONTEXT) { - contextCreateInfo.forward = true; + contextCreateInfo.forward = PAL_TRUE; } if (info->extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { @@ -250,21 +210,18 @@ bool openglContextTest() } // create the opengl context with the context create info + PalGLContext* context = nullptr; result = palCreateGLContext(&contextCreateInfo, &context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create opengl context: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to create opengl context"); + return PAL_FALSE; } // make the context current and optionally set vsync if supported result = palMakeContextCurrent(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to make opengl context current: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to make opengl context current"); + return PAL_FALSE; } if (info->extensions & PAL_GL_EXTENSION_SWAP_CONTROL) { @@ -275,13 +232,13 @@ bool openglContextTest() // load function procs PFNGLCLEARCOLORPROC glClearColor = nullptr; PFNGLCLEARPROC glClear = nullptr; - glClearColor = (PFNGLCLEARCOLORPROC)palGLGetProcAddress("glClearColor"); - glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); + glClearColor = (PFNGLCLEARCOLORPROC)palGetGLProcAddress("glClearColor"); + glClear = (PFNGLCLEARPROC)palGetGLProcAddress("glClear"); // set clear color glClearColor(0.2f, 0.2f, 0.2f, 1.0f); - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -289,16 +246,16 @@ bool openglContextTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -311,10 +268,8 @@ bool openglContextTest() // swap buffers result = palSwapBuffers(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to swap buffers: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to swap buffers"); + return PAL_FALSE; } } @@ -336,5 +291,5 @@ bool openglContextTest() // free the framebuffer configs palFree(nullptr, fbConfigs); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/opengl_fbconfig_test.c b/tests/opengl/opengl_fbconfig_test.c similarity index 70% rename from tests/opengl_fbconfig_test.c rename to tests/opengl/opengl_fbconfig_test.c index ebd14ffd..7e217696 100644 --- a/tests/opengl_fbconfig_test.c +++ b/tests/opengl/opengl_fbconfig_test.c @@ -1,72 +1,74 @@ -#include "pal/pal_opengl.h" -#include "pal/pal_video.h" // for window +#include "pal2/pal_opengl.h" +#include "pal2/pal_video.h" // for window #include "tests.h" static const char* g_BoolsToSting[2] = {"False", "True"}; -bool openglFBConfigTest() +PalBool openglFBConfigTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Opengl FBConfig Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - // initialize the video system and create a window - PalResult result = palInitVideo(nullptr, nullptr); + PalResult result = palInitVideo(nullptr, nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } - // get the instance or display handle and pass it to the opengl system - // This must be called before the opengl system is initialized - palGLSetInstance(palGetInstance()); + // check if opengl API is supported or fallback to opengl es + PalGLAPI openglAPI; + void* videoInstance = palGetInstance(); + const PalBool* supportedAPIs = palGetSupportedGLAPIs(videoInstance); + if (supportedAPIs) { + if (supportedAPIs[PAL_GL_API_OPENGL]) { + openglAPI = PAL_GL_API_OPENGL; + + } else { + openglAPI = PAL_GL_API_OPENGL_ES; + } + + } else { + palLog(nullptr, "Failed to get supported opengl apis"); + return PAL_FALSE; + } - // initialize the opengl system - result = palInitGL(nullptr); + // initialize the opengl system. This loads the icd. + result = palInitGL(openglAPI, videoInstance, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; + logResult(result, "Failed to initialize opengl"); + return PAL_FALSE; } // enumerate supported opengl framebuffer configs - Int32 fbCount = 0; - result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); + uint32_t fbCount = 0; + result = palEnumerateGLFBConfigs(&fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } palLog(nullptr, "GL FBConfig count: %d", fbCount); if (fbCount == 0) { palLog(nullptr, "No supported FBConfig found"); - return false; + return PAL_FALSE; } PalGLFBConfig* fbConfigs = nullptr; fbConfigs = palAllocate(nullptr, sizeof(PalGLFBConfig) * fbCount, 0); if (!fbConfigs) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // enumerate supported opengl framebuffer configs - result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); + result = palEnumerateGLFBConfigs(&fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } // log configs palLog(nullptr, ""); - for (Int32 i = 0; i < fbCount; i++) { + for (int32_t i = 0; i < fbCount; i++) { // log pixel formate PalGLFBConfig* config = &fbConfigs[i]; palLog(nullptr, "FB Config Index: %d", config->index); @@ -97,9 +99,9 @@ bool openglFBConfigTest() desired.stencilBits = 8; desired.samples = 2; - desired.stereo = false; // not widely supported - desired.sRGB = true; - desired.doubleBuffer = true; + desired.stereo = PAL_FALSE; // not widely supported + desired.sRGB = PAL_TRUE; + desired.doubleBuffer = PAL_TRUE; // get the closest const PalGLFBConfig* closest = nullptr; @@ -144,5 +146,5 @@ bool openglFBConfigTest() // free the framebuffer configs palFree(nullptr, fbConfigs); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/opengl_multi_context_test.c b/tests/opengl/opengl_multi_context_test.c similarity index 58% rename from tests/opengl_multi_context_test.c rename to tests/opengl/opengl_multi_context_test.c index 70c3cd19..1ff6be3f 100644 --- a/tests/opengl_multi_context_test.c +++ b/tests/opengl/opengl_multi_context_test.c @@ -1,6 +1,6 @@ -#include "pal/pal_opengl.h" -#include "pal/pal_video.h" // for window +#include "pal2/pal_opengl.h" +#include "pal2/pal_video.h" // for window #include "tests.h" static const char* g_BoolsToSting[2] = {"False", "True"}; @@ -12,95 +12,86 @@ typedef void(PAL_GL_APIENTRY* PFNGLCLEARCOLORPROC)( float blue, float alpha); -typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)( - Uint32 mask); // use GL typedefs if needed +typedef void(PAL_GL_APIENTRY* PFNGLCLEARPROC)(uint32_t mask); // use GL typedefs if needed -bool openglMultiContextTest() +PalBool openglMultiContextTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Opengl Multi Context Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = nullptr; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } - // get the instance or display handle and pass it to the opengl system - // This must be called before the opengl system is initialized - palGLSetInstance(palGetInstance()); + // check if opengl API is supported or fallback to opengl es + PalGLAPI openglAPI; + void* videoInstance = palGetInstance(); + const PalBool* supportedAPIs = palGetSupportedGLAPIs(videoInstance); + if (supportedAPIs) { + if (supportedAPIs[PAL_GL_API_OPENGL]) { + openglAPI = PAL_GL_API_OPENGL; - // initialize the opengl system - result = palInitGL(nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; + } else { + openglAPI = PAL_GL_API_OPENGL_ES; + } + + } else { + palLog(nullptr, "Failed to get supported opengl apis"); + return PAL_FALSE; } - PalWindow* window = nullptr; - PalGLContext* context = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalGLContextCreateInfo contextCreateInfo = {0}; - Int32 fbCount = 0; - bool running = false; + // initialize the opengl system. This loads the icd. + result = palInitGL(openglAPI, videoInstance, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize opengl"); + return PAL_FALSE; + } // enumerate supported opengl framebuffer configs - // glWindow must be nullptr - result = palEnumerateGLFBConfigs(nullptr, &fbCount, nullptr); + uint32_t fbCount = 0; + result = palEnumerateGLFBConfigs(&fbCount, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } palLog(nullptr, "GL FBConfig count: %d", fbCount); if (fbCount == 0) { palLog(nullptr, "No supported FBConfig found"); - return false; + return PAL_FALSE; } PalGLFBConfig* fbConfigs = nullptr; fbConfigs = palAllocate(nullptr, sizeof(PalGLFBConfig) * fbCount, 0); if (!fbConfigs) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // enumerate supported opengl framebuffer configs - // glWindow must be nullptr - result = palEnumerateGLFBConfigs(nullptr, &fbCount, fbConfigs); + result = palEnumerateGLFBConfigs(&fbCount, fbConfigs); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to query GL FBConfigs: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to query GL FBConfigs"); + return PAL_FALSE; } // we desire a FB config and see what is closest the driver will give us @@ -114,9 +105,9 @@ bool openglMultiContextTest() desired.stencilBits = 8; desired.samples = 2; - desired.stereo = false; // not widely supported - desired.sRGB = true; - desired.doubleBuffer = true; + desired.stereo = PAL_FALSE; // not widely supported + desired.sRGB = PAL_TRUE; + desired.doubleBuffer = PAL_TRUE; // get the closest const PalGLFBConfig* closest = nullptr; @@ -138,80 +129,48 @@ bool openglMultiContextTest() palLog(nullptr, " sRGB: %s", g_BoolsToSting[closest->sRGB]); palLog(nullptr, ""); - // set the FBConfig that will be used by PAL video system - // to create windows. this must be set before creating a window - // for this example, we set the closest we desired. - // If pal_opengl and pal_video will be used together, - // then its recommended to use PAL_CONFIG_BACKEND_PAL_OPENGL - - // NOTE: If PAL video system will not be used, - // users need to call the direct OS call to achieve this. - result = palSetFBConfig(closest->index, PAL_CONFIG_BACKEND_PAL_OPENGL); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set GL FBConfig: %s", error); - return false; - } - - // if not using pal_opengl with pal_video - // we get the backend string from the opengl system and - // get the backend from it - // Possible values are `wgl`, `glx`, `gles`, `egl`. - // PalFBConfigBackend backend; - // const char* glBackendString = palGLGetBackend(); - // if (strcmp(glBackendString, "wgl") == 0) { - // backend = PAL_CONFIG_BACKEND_WGL; - - // } else if (strcmp(glBackendString, "glx") == 0) { - // backend = PAL_CONFIG_BACKEND_GLX; - - // } else if (strcmp(glBackendString, "gles") == 0) { - // backend = PAL_CONFIG_BACKEND_GLES; - - // } else if (strcmp(glBackendString, "egl") == 0) { - // backend = PAL_CONFIG_BACKEND_EGL; - // } - + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Opengl Multi Context Window"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } + // set the backend and the fbConfig. We use PAL_FBCONFIG_BACKEND_PAL_OPENGL + // because we are using both pal_video and pal_opengl + createInfo.fbConfigBackend = PAL_FBCONFIG_BACKEND_PAL_OPENGL; + createInfo.fbConfigIndex = closest->index; + // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // get window handle. You can use any window from any library // so long as you can get the window handle and display (if on X11, wayland) // If pal video system will not be used, there is no need to initialize it - PalWindowHandleInfoEx winHandle = {0}; - winHandle = palGetWindowHandleInfoEx(window); + PalWindowHandleInfo winHandle = {0}; + palGetWindowHandleInfo(window, &winHandle); // PalGLWindow is just a struct to hold native handles PalGLWindow glWindow = {0}; - glWindow.display = winHandle.nativeDisplay; + glWindow.instance = winHandle.nativeInstance; // On Wayland the window is the wl_egl_window if (winHandle.nativeHandle3) { @@ -226,11 +185,12 @@ bool openglMultiContextTest() const PalGLInfo* info = palGetGLInfo(); // fill the context create info with the closest FBConfig - contextCreateInfo.debug = true; // debug context + PalGLContextCreateInfo contextCreateInfo = {0}; + contextCreateInfo.debug = PAL_TRUE; // debug context contextCreateInfo.fbConfig = closest; // we use the closest to what we want contextCreateInfo.major = info->major; // context major contextCreateInfo.minor = info->minor; // context minor - contextCreateInfo.noError = false; // check PAL_GL_EXTENSION_NO_ERROR + contextCreateInfo.noError = PAL_FALSE; // check PAL_GL_EXTENSION_NO_ERROR // check PAL_GL_EXTENSION_FLUSH_CONTROL contextCreateInfo.release = PAL_GL_RELEASE_BEHAVIOR_NONE; @@ -242,7 +202,7 @@ bool openglMultiContextTest() contextCreateInfo.window = &glWindow; if (info->extensions & PAL_GL_EXTENSION_CREATE_CONTEXT) { - contextCreateInfo.forward = true; + contextCreateInfo.forward = PAL_TRUE; } if (info->extensions & PAL_GL_EXTENSION_CONTEXT_PROFILE) { @@ -250,21 +210,18 @@ bool openglMultiContextTest() } // create the opengl context with the context create info + PalGLContext* context = nullptr; result = palCreateGLContext(&contextCreateInfo, &context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create opengl context: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to create opengl context"); + return PAL_FALSE; } // make the context current and optionally set vsync if supported result = palMakeContextCurrent(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to make opengl context current: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to make opengl context current"); + return PAL_FALSE; } if (info->extensions & PAL_GL_EXTENSION_SWAP_CONTROL) { @@ -275,13 +232,13 @@ bool openglMultiContextTest() // load function procs PFNGLCLEARCOLORPROC glClearColor = nullptr; PFNGLCLEARPROC glClear = nullptr; - glClearColor = (PFNGLCLEARCOLORPROC)palGLGetProcAddress("glClearColor"); - glClear = (PFNGLCLEARPROC)palGLGetProcAddress("glClear"); + glClearColor = (PFNGLCLEARCOLORPROC)palGetGLProcAddress("glClearColor"); + glClear = (PFNGLCLEARPROC)palGetGLProcAddress("glClear"); // set clear color glClearColor(0.2f, 0.2f, 0.2f, 1.0f); - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -289,16 +246,16 @@ bool openglMultiContextTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -311,10 +268,8 @@ bool openglMultiContextTest() // swap buffers result = palSwapBuffers(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to swap buffers: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to swap buffers"); + return PAL_FALSE; } } @@ -326,39 +281,35 @@ bool openglMultiContextTest() context = nullptr; result = palCreateGLContext(&contextCreateInfo, &context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create opengl context: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to create opengl context"); + return PAL_FALSE; } // make the context current on this thread result = palMakeContextCurrent(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to make opengl context current: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to make opengl context current"); + return PAL_FALSE; } glClearColor(.2f, .6f, .6f, 1.0f); - running = true; + running = PAL_TRUE; while (running) { palUpdateVideo(); PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -368,10 +319,8 @@ bool openglMultiContextTest() glClear(0x00004000); // GL_COLOR_BUFFER_BIT result = palSwapBuffers(&glWindow, context); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to swap buffers: %s", error); - palFree(nullptr, fbConfigs); - return false; + logResult(result, "Failed to swap buffers"); + return PAL_FALSE; } } @@ -393,5 +342,5 @@ bool openglMultiContextTest() // free the framebuffer configs palFree(nullptr, fbConfigs); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/opengl_test.c b/tests/opengl/opengl_test.c similarity index 67% rename from tests/opengl_test.c rename to tests/opengl/opengl_test.c index a61d7f0e..d7c062fd 100644 --- a/tests/opengl_test.c +++ b/tests/opengl/opengl_test.c @@ -1,34 +1,39 @@ -#include "pal/pal_opengl.h" -#include "pal/pal_video.h" +#include "pal2/pal_opengl.h" +#include "pal2/pal_video.h" #include "tests.h" -bool openglTest() +PalBool openglTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Opengl Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - // initialize the video system and create a window - PalResult result = palInitVideo(nullptr, nullptr); + // initialize the video system + PalResult result = palInitVideo(nullptr, nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } - // get the instance or display handle and pass it to the opengl system - // This must be called before the opengl system is initialized - palGLSetInstance(palGetInstance()); + // check if opengl API is supported or fallback to opengl es + PalGLAPI openglAPI; + void* videoInstance = palGetInstance(); + const PalBool* supportedAPIs = palGetSupportedGLAPIs(videoInstance); + if (supportedAPIs) { + if (supportedAPIs[PAL_GL_API_OPENGL]) { + openglAPI = PAL_GL_API_OPENGL; + + } else { + openglAPI = PAL_GL_API_OPENGL_ES; + } + + } else { + palLog(nullptr, "Failed to get supported opengl apis"); + return PAL_FALSE; + } // initialize the opengl system. This loads the icd. - result = palInitGL(nullptr); + result = palInitGL(openglAPI, videoInstance, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize opengl: %s", error); - return false; + logResult(result, "Failed to initialize opengl"); + return PAL_FALSE; } // get the icd info. max version, graphics driver etc @@ -89,5 +94,5 @@ bool openglTest() // shutdown the video system palShutdownVideo(); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/system_test.c b/tests/system/cpu_test.c similarity index 51% rename from tests/system_test.c rename to tests/system/cpu_test.c index 126c085d..4ee60358 100644 --- a/tests/system_test.c +++ b/tests/system/cpu_test.c @@ -1,45 +1,8 @@ -#include "pal/pal_system.h" +#include "pal2/pal_system.h" #include "tests.h" - #include // for strcat -static inline const char* platformToString(PalPlatformType type) -{ - switch (type) { - case PAL_PLATFORM_WINDOWS: - return "Windows"; - - case PAL_PLATFORM_LINUX: - return "Linux"; - - case PAL_PLATFORM_MACOS: - return "MacOs"; - - case PAL_PLATFORM_ANDROID: - return "Android"; - - case PAL_PLATFORM_IOS: - return "Ios"; - } - return nullptr; -} - -static inline const char* platformApiToString(PalPlatformApiType type) -{ - switch (type) { - case PAL_PLATFORM_API_WIN32: - return "Win32"; - - case PAL_PLATFORM_API_X11: - return "X11"; - - case PAL_PLATFORM_API_WAYLAND: - return "Wayland"; - } - return nullptr; -} - static inline const char* cpuArchToString(PalCpuArch arch) { switch (arch) { @@ -61,51 +24,15 @@ static inline const char* cpuArchToString(PalCpuArch arch) return nullptr; } -bool systemTest() +PalBool cpuTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "System Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; PalCPUInfo cpuInfo; - PalPlatformInfo platformInfo; - - // get the platform info. Users must cache this - result = palGetPlatformInfo(&platformInfo); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get platform info: %s", error); - return false; - } - - // user defined allocator, set to override the default one or nullptr for - // default - result = palGetCPUInfo(nullptr, &cpuInfo); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get Cpu info: %s", error); - return false; - } - // log platform information - palLog(nullptr, "Platform: %s", platformToString(platformInfo.type)); - palLog(nullptr, " Name: %s", platformInfo.name); - palLog(nullptr, " API: %s", platformApiToString(platformInfo.apiType)); - palLog(nullptr, " Total RAM: %llu MB", platformInfo.totalRAM); - palLog(nullptr, " Total Memory: %llu GB", platformInfo.totalMemory); - - Uint16 major, minor, build; - major = platformInfo.version.major; - minor = platformInfo.version.minor; - build = platformInfo.version.build; - palLog(nullptr, " Version: (%d.%d.%d)", major, minor, build); - - // log cpu information + // user defined allocator, set to override the default one or nullptr for default + palGetCPUInfo(nullptr, &cpuInfo); const char* archString = cpuArchToString(cpuInfo.architecture); - Int32 processors = cpuInfo.numLogicalProcessors; + int32_t processors = cpuInfo.numLogicalProcessors; palLog(nullptr, " Cpu: %s", cpuInfo.model); palLog(nullptr, " Vendor: %s", cpuInfo.vendor); @@ -168,5 +95,5 @@ bool systemTest() palLog(nullptr, " Instructions Sets: %s", instructionSets); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/system/platform_test.c b/tests/system/platform_test.c new file mode 100644 index 00000000..7986eb91 --- /dev/null +++ b/tests/system/platform_test.c @@ -0,0 +1,62 @@ + +#include "pal2/pal_system.h" +#include "tests.h" + +static inline const char* platformToString(PalPlatformType type) +{ + switch (type) { + case PAL_PLATFORM_TYPE_WINDOWS: + return "Windows"; + + case PAL_PLATFORM_TYPE_LINUX: + return "Linux"; + + case PAL_PLATFORM_TYPE_MACOS: + return "MacOs"; + + case PAL_PLATFORM_TYPE_ANDROID: + return "Android"; + + case PAL_PLATFORM_TYPE_IOS: + return "Ios"; + } + return nullptr; +} + +static inline const char* platformApiToString(PalPlatformApiType type) +{ + switch (type) { + case PAL_PLATFORM_API_TYPE_WIN32: + return "Win32"; + + case PAL_PLATFORM_API_TYPE_X11: + return "X11"; + + case PAL_PLATFORM_API_TYPE_WAYLAND: + return "Wayland"; + } + return nullptr; +} + +PalBool platformTest() +{ + PalResult result; + PalPlatformInfo platformInfo; + + // get the platform info. Users must cache this + palGetPlatformInfo(&platformInfo); + + palLog(nullptr, "Platform: %s", platformToString(platformInfo.type)); + palLog(nullptr, " Name: %s", platformInfo.name); + palLog(nullptr, " API: %s", platformApiToString(platformInfo.apiType)); + palLog(nullptr, " Total RAM: %llu MB", platformInfo.totalRAM); + palLog(nullptr, " Total Memory: %llu GB", platformInfo.totalMemory); + + uint16_t major, minor, build; + major = platformInfo.version.major; + minor = platformInfo.version.minor; + build = platformInfo.version.build; + palLog(nullptr, " Version: (%d.%d.%d)", major, minor, build); + + return PAL_TRUE; +} diff --git a/tests/tests.c b/tests/tests.c index 0b470049..ccf263f5 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -1,44 +1,44 @@ #include "tests.h" -#define MAX_TESTS 32 // will change +#define MAX_TESTS 64 // will change typedef struct { TestFn func; const char* name; } TestEntry; -typedef struct { - TestEntry tests[MAX_TESTS]; - Int32 count; -} Tests; - -static Tests s_Test; +static uint32_t s_Count = 0; +static TestEntry s_Test[MAX_TESTS]; static const char* s_FailedString = "FAILED"; static const char* s_PassedString = "PASSED"; void registerTest( - const char* name, - TestFn func) + TestFn func, + const char* name) { - TestEntry* entry = &s_Test.tests[s_Test.count++]; - entry->func = func; - entry->name = name; + TestEntry entry = {func, name}; + s_Test[s_Count++] = entry; } void runTests() { - bool status = false; + PalBool status = PAL_FALSE; const char* statusString = nullptr; - for (Int32 i = 0; i < s_Test.count; i++) { - status = s_Test.tests[i].func(); - + for (int32_t i = 0; i < s_Count; i++) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, s_Test[i].name); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + + status = s_Test[i].func(); if (status) { statusString = s_PassedString; } else { statusString = s_FailedString; } - palLog(nullptr, "%s: %s", s_Test.tests[i].name, statusString); + palLog(nullptr, statusString); } -} \ No newline at end of file +} diff --git a/tests/tests.h b/tests/tests.h index 65eb69c8..e6203f84 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -2,55 +2,108 @@ #ifndef _TESTS_H #define _TESTS_H -#include "pal/pal_core.h" +#include "pal2/pal_core.h" +#include -typedef bool (*TestFn)(); +typedef PalBool (*TestFn)(); void registerTest( - const char* name, - TestFn func); - + TestFn func, + const char* name); void runTests(); +static PalBool readFile( + const char* filename, + void* buffer, + uint32_t* size) +{ + FILE* file = fopen(filename, "rb"); + if (!file) { + return PAL_FALSE; + } + + fseek(file, 0, SEEK_END); + uint32_t tmpSize = (uint32_t)ftell(file); + fseek(file, 0, SEEK_SET); + + if (buffer) { + tmpSize = *size; + size_t read = fread(buffer, 1, tmpSize, file); + if ((uint32_t)read != tmpSize) { + return PAL_FALSE; + } + } + + fclose(file); + *size = tmpSize; + return PAL_TRUE; +} + +static inline void logResult( + PalResult result, + const char* msg) +{ + char buffer[256]; + palFormatResult(result, 256, buffer); + palLog(nullptr, "%s \n %s", msg, buffer); +} + // core tests -bool loggerTest(); -bool timeTest(); -bool userEventTest(); -bool eventTest(); +PalBool loggerTest(); +PalBool timeTest(); +PalBool userEventTest(); +PalBool eventTest(); // system tests -bool systemTest(); +PalBool platformTest(); +PalBool cpuTest(); // system tests -bool threadTest(); -bool tlsTest(); -bool mutexTest(); -bool condvarTest(); +PalBool threadTest(); +PalBool tlsTest(); +PalBool mutexTest(); +PalBool condvarTest(); // video test -bool videoTest(); -bool monitorTest(); -bool monitorModeTest(); -bool windowTest(); -bool iconTest(); -bool cursorTest(); -bool inputWindowTest(); -bool systemCursorTest(); -bool attachWindowTest(); -bool charEventTest(); -bool nativeIntegrationTest(); -bool nativeInstanceTest(); -bool customDecorationTest(); +PalBool videoTest(); +PalBool monitorTest(); +PalBool monitorModeTest(); +PalBool windowTest(); +PalBool iconTest(); +PalBool cursorTest(); +PalBool inputWindowTest(); +PalBool systemCursorTest(); +PalBool attachWindowTest(); +PalBool charEventTest(); +PalBool nativeIntegrationTest(); +PalBool nativeInstanceTest(); +PalBool customDecorationTest(); // opengl test -bool openglTest(); +PalBool openglTest(); // opengl and video test -bool openglFBConfigTest(); -bool openglContextTest(); -bool openglMultiContextTest(); +PalBool openglFBConfigTest(); +PalBool openglContextTest(); +PalBool openglMultiContextTest(); // opengl, video and thread -bool multiThreadOpenGlTest(); +PalBool multiThreadOpenGlTest(); + +// graphics +PalBool graphicsTest(); +PalBool computeTest(); +PalBool rayTracingTest(); +PalBool multiDescriptorSetTest(); +PalBool customBackendTest(); + +// graphics and video +PalBool clearColorTest(); +PalBool triangleTest(); +PalBool meshTest(); +PalBool textureTest(); +PalBool geometryTest(); +PalBool indirectDrawTest(); +PalBool descriptorIndexingTest(); -#endif // _TESTS_H \ No newline at end of file +#endif // _TESTS_H diff --git a/tests/tests.lua b/tests/tests.lua index 1c196956..4564368f 100644 --- a/tests/tests.lua +++ b/tests/tests.lua @@ -1,67 +1,97 @@ project "tests" - language "C" kind "ConsoleApp" - targetdir(target_dir) - objdir(obj_dir) + targetdir(targetDir) + objdir(objDir) - files { + files { "tests_main.c", "tests.c", - "logger_test.c", - "time_test.c", - "user_event_test.c", - "event_test.c" + + -- core + "core/logger_test.c", + "core/time_test.c", + + -- event + "event/user_event_test.c", + "event/event_test.c" } - if (PAL_BUILD_SYSTEM) then - files { - "system_test.c" + if (PAL_BUILD_SYSTEM_MODULE) then + files { + "system/platform_test.c", + "system/cpu_test.c" + } + end + + if (PAL_BUILD_THREAD_MODULE) then + files { + "thread/thread_test.c", + "thread/tls_test.c", + "thread/mutex_test.c", + "thread/condvar_test.c" } end - if (PAL_BUILD_THREAD) then - files { - "thread_test.c", - "tls_test.c", - "mutex_test.c", - "condvar_test.c" + if (PAL_BUILD_VIDEO_MODULE) then + files { + "video/video_test.c", + "video/monitor_test.c", + "video/monitor_mode_test.c", + "video/window_test.c", + "video/icon_test.c", + "video/cursor_test.c", + "video/input_window_test.c", + "video/system_cursor_test.c", + "video/attach_window_test.c", + "video/char_event_test.c", + "video/native_integration_test.c", + "video/native_instance_test.c", + "video/custom_decoration_test.c" } end - if (PAL_BUILD_VIDEO) then - files { - "video_test.c", - "monitor_test.c", - "monitor_mode_test.c", - "window_test.c", - "icon_test.c", - "cursor_test.c", - "input_window_test.c", - "system_cursor_test.c", - "attach_window_test.c", - "char_event_test.c", - "native_integration_test.c", - "native_instance_test.c", - "custom_decoration_test.c" + if (PAL_BUILD_OPENGL_MODULE and PAL_BUILD_VIDEO_MODULE) then + files { + "opengl/opengl_test.c", + "opengl/opengl_fbconfig_test.c", + "opengl/opengl_context_test.c", + "opengl/opengl_multi_context_test.c" } end - if (PAL_BUILD_OPENGL and PAL_BUILD_VIDEO) then - files { - "opengl_test.c", - "opengl_fbconfig_test.c", - "opengl_context_test.c", - "opengl_multi_context_test.c" + if (PAL_BUILD_OPENGL_MODULE and PAL_BUILD_VIDEO_MODULE and PAL_BUILD_THREAD_MODULE) then + files { + "opengl/multi_thread_opengl_test.c" } end - if (PAL_BUILD_OPENGL and PAL_BUILD_VIDEO and PAL_BUILD_THREAD) then - files { - "multi_thread_opengl_test.c" + if (PAL_BUILD_GRAPHICS_MODULE) then + files { + "graphics/graphics_test.c", + "graphics/compute_test.c", + "graphics/ray_tracing_test.c", + "graphics/multi_descriptor_set_test.c", + "graphics/custom_backend_test.c" } end - includedirs { "%{wks.location}/include" } - links { "PAL" } \ No newline at end of file + if (PAL_BUILD_GRAPHICS_MODULE and PAL_BUILD_VIDEO_MODULE and PAL_BUILD_SYSTEM_MODULE) then + files { + "graphics/clear_color_test.c", + "graphics/triangle_test.c", + "graphics/mesh_test.c", + "graphics/texture_test.c", + "graphics/geometry_test.c", + "graphics/indirect_draw_test.c", + "graphics/descriptor_indexing_test.c" + } + end + + includedirs { + "%{wks.location}/include", + "%{wks.location}/tests" + } + + links { "PAL2" } diff --git a/tests/tests_main.c b/tests/tests_main.c index a551b677..945b964f 100644 --- a/tests/tests_main.c +++ b/tests/tests_main.c @@ -1,5 +1,4 @@ -#include "pal/pal_config.h" // for systems reflection #include "tests.h" // clang-format off @@ -9,51 +8,71 @@ int main(int argc, char** argv) palLog(nullptr, "%s: %s", "PAL Version", palGetVersionString()); // core - registerTest("Logger Test", loggerTest); - registerTest("Time Test", timeTest); - registerTest("User Event Test", userEventTest); - registerTest("Event Test", eventTest); - -#if PAL_HAS_SYSTEM - registerTest("System Test", systemTest); -#endif // PAL_HAS_SYSTEM - -#if PAL_HAS_THREAD - registerTest("Thread Test", threadTest); - registerTest("TLS Test", tlsTest); - registerTest("Mutex Test", mutexTest); - registerTest("Condvar Test", condvarTest); -#endif // PAL_HAS_THREAD - -#if PAL_HAS_VIDEO - registerTest("Video Test", videoTest); - registerTest("Monitor Test", monitorTest); - registerTest("Monitor Mode Test", monitorModeTest); - registerTest("Window Test", windowTest); - registerTest("Icon Test", iconTest); - registerTest("Cursor Test", cursorTest); - registerTest("Input Window Test", inputWindowTest); - registerTest("System Cursor Test", systemCursorTest); - registerTest("Attach Window Test", attachWindowTest); - registerTest("Character Event Test", charEventTest); - registerTest("Native Integration Test", nativeIntegrationTest); - registerTest("Native Instance Test", nativeInstanceTest); - registerTest("Custom Decoration Test", customDecorationTest); -#endif // PAL_HAS_VIDEO - - // This test can run without video system so long as your have a valid - // window -#if PAL_HAS_OPENGL && PAL_HAS_VIDEO - registerTest("Opengl Test", openglTest); - registerTest("Opengl FBConfig Test", openglFBConfigTest); - registerTest("Opengl Context Test", openglContextTest); - registerTest("Opengl Multi Context Test", openglMultiContextTest); -#endif // PAL_HAS_OPENGL - -#if PAL_HAS_OPENGL && PAL_HAS_VIDEO && PAL_HAS_THREAD - registerTest("Multi Thread OpenGL Test", multiThreadOpenGlTest); -#endif // + registerTest(loggerTest, "Logger Test"); + registerTest(timeTest, "Time Test"); + + // event + registerTest(eventTest, "Event test"); + registerTest(userEventTest, "User Event Test"); + +#if PAL_HAS_SYSTEM_MODULE == 1 + registerTest(platformTest, "Platform Test"); + registerTest(cpuTest, "CPU Test"); +#endif // PAL_HAS_SYSTEM_MODULE + +#if PAL_HAS_THREAD_MODULE == 1 + registerTest(threadTest, "Thread Test"); + registerTest(tlsTest, "TLS Test"); + registerTest(mutexTest, "Mutex Test"); + registerTest(condvarTest, "Condvar Test"); +#endif // PAL_HAS_THREAD_MODULE + +#if PAL_HAS_VIDEO_MODULE == 1 + registerTest(videoTest, "Video Test"); + registerTest(monitorTest, "Monitor Test"); + registerTest(monitorModeTest, "Monitor Mode Test"); + registerTest(windowTest, "Window Test"); + registerTest(iconTest, "Icon Test"); + registerTest(cursorTest, "Cursor Test"); + registerTest(inputWindowTest, "Input Window Test"); + registerTest(systemCursorTest, "System Cursor Test"); + registerTest(attachWindowTest, "Attach Window Test"); + registerTest(charEventTest, "Char Event Test"); + registerTest(nativeIntegrationTest, "Native Integration Test"); + registerTest(nativeInstanceTest, "Native Instance Test"); + registerTest(customDecorationTest, "Custom Decoration Test"); +#endif // PAL_HAS_VIDEO_MODULE + + // This test can run without video system so long as your have a valid window +#if PAL_HAS_OPENGL_MODULE == 1 && PAL_HAS_VIDEO_MODULE == 1 + registerTest(openglTest, "Opengl Test"); + registerTest(openglFBConfigTest, "Opengl FBConfig Test"); + registerTest(openglContextTest, "Context Test"); + registerTest(openglMultiContextTest, "Opengl Multi Context Test"); +#endif // PAL_HAS_OPENGL_MODULE + +#if PAL_HAS_OPENGL_MODULE == 1 && PAL_HAS_VIDEO_MODULE == 1 && PAL_HAS_THREAD_MODULE == 1 + registerTest(multiThreadOpenGlTest, "Multi Thread Opengl Test"); +#endif // PAL_HAS_OPENGL_MODULE + +#if PAL_HAS_GRAPHICS_MODULE == 1 + registerTest(graphicsTest, "Graphics Test"); + registerTest(computeTest, "Compute Test"); + registerTest(rayTracingTest, "Ray Tracing Test"); + registerTest(multiDescriptorSetTest, "Multi Descriptor Set Test"); + registerTest(customBackendTest, "Custom Backend Test"); +#endif // PAL_HAS_GRAPHICS_MODULE + +#if PAL_HAS_GRAPHICS_MODULE == 1 && PAL_HAS_VIDEO_MODULE == 1 && PAL_HAS_SYSTEM_MODULE == 1 + registerTest(clearColorTest, "Clear Color Test"); + registerTest(triangleTest, "Triangle Test"); + registerTest(meshTest, "Mesh Test"); + registerTest(textureTest, "Texture Test"); + registerTest(geometryTest, "Geometry Test"); + registerTest(indirectDrawTest, "Indirect Draw Test"); + registerTest(descriptorIndexingTest, "Descriptor Indexing Test"); +#endif // PAL_HAS_GRAPHICS_MODULE runTests(); return 0; -} \ No newline at end of file +} diff --git a/tests/condvar_test.c b/tests/thread/condvar_test.c similarity index 71% rename from tests/condvar_test.c rename to tests/thread/condvar_test.c index 266b7078..83a200c1 100644 --- a/tests/condvar_test.c +++ b/tests/thread/condvar_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_thread.h" +#include "pal2/pal_thread.h" #include "tests.h" #define THREAD_COUNT 4 @@ -9,8 +9,8 @@ static PalMutex* g_Mutex; static PalCondVar* g_Condition; typedef struct { - bool ready; - Uint32 id; + PalBool ready; + uint32_t id; } ThreadData; static void* PAL_CALL worker(void* arg) @@ -28,14 +28,8 @@ static void* PAL_CALL worker(void* arg) return nullptr; } -bool condvarTest() +PalBool condvarTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Condvar Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; PalThread* threads[THREAD_COUNT]; @@ -43,23 +37,21 @@ bool condvarTest() data = palAllocate(nullptr, sizeof(ThreadData) * THREAD_COUNT, 0); if (!data) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // create mutex result = palCreateMutex(nullptr, &g_Mutex); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create mutex: %s", error); - return false; + logResult(result, "Failed to create mutex"); + return PAL_FALSE; } // create condition result = palCreateCondVar(nullptr, &g_Condition); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create cond var: %s", error); - return false; + logResult(result, "Failed to create condition variable"); + return PAL_FALSE; } // create threads @@ -67,17 +59,16 @@ bool condvarTest() createInfo.entry = worker; // will be the same for all threads createInfo.stackSize = 0; // same for all threads createInfo.allocator = nullptr; // default - for (Int32 i = 0; i < THREAD_COUNT; i++) { + for (int32_t i = 0; i < THREAD_COUNT; i++) { ThreadData* threadData = &data[i]; threadData->id = i + 1; - threadData->ready = false; + threadData->ready = PAL_FALSE; createInfo.arg = (void*)threadData; result = palCreateThread(&createInfo, &threads[i]); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } // since thread priority is not supported on all platforms @@ -93,7 +84,7 @@ bool condvarTest() // signal thread 1 palLockMutex(g_Mutex); - data[0].ready = true; + data[0].ready = PAL_TRUE; palSignalCondVar(g_Condition); palUnlockMutex(g_Mutex); @@ -103,8 +94,8 @@ bool condvarTest() // broadcast to all remaining threads palLockMutex(g_Mutex); - for (Int32 i = 1; i < THREAD_COUNT; i++) { - data[i].ready = true; + for (int32_t i = 1; i < THREAD_COUNT; i++) { + data[i].ready = PAL_TRUE; } palBroadcastCondVar(g_Condition); @@ -112,11 +103,11 @@ bool condvarTest() // wait for the remaining threads // joint threads does not need to be detached - for (Int32 i = 0; i < THREAD_COUNT; i++) { + for (int32_t i = 0; i < THREAD_COUNT; i++) { palJoinThread(threads[i], nullptr); palLog(nullptr, "Thread %d finished successfully", data[i].id); } palFree(nullptr, data); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/mutex_test.c b/tests/thread/mutex_test.c similarity index 69% rename from tests/mutex_test.c rename to tests/thread/mutex_test.c index 048d8aa0..4bbafdab 100644 --- a/tests/mutex_test.c +++ b/tests/thread/mutex_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_thread.h" +#include "pal2/pal_thread.h" #include "tests.h" #define MAX_COUNTER 10000 @@ -7,7 +7,7 @@ typedef struct { PalMutex* mutex; - Int32 counter; + int32_t counter; } SharedData; static void* PAL_CALL worker(void* arg) @@ -16,7 +16,7 @@ static void* PAL_CALL worker(void* arg) // this is only needed when two or more threads are writing to the same // variable - for (Int32 i = 0; i < MAX_COUNTER; i++) { + for (int32_t i = 0; i < MAX_COUNTER; i++) { palLockMutex(data->mutex); data->counter++; // a shared variable. we need lock and unlocks palLog(nullptr, "Counter: %d", data->counter); @@ -25,14 +25,8 @@ static void* PAL_CALL worker(void* arg) return nullptr; } -bool mutexTest() +PalBool mutexTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Mutex Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; PalThread* threads[THREAD_COUNT]; PalMutex* mutex = nullptr; @@ -40,15 +34,14 @@ bool mutexTest() SharedData* data = palAllocate(nullptr, sizeof(SharedData), 0); if (!data) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // create mutex result = palCreateMutex(nullptr, &mutex); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create mutex: %s", error); - return false; + logResult(result, "Failed to create mutex"); + return PAL_FALSE; } data->counter = 0; @@ -59,21 +52,20 @@ bool mutexTest() createInfo.entry = worker; // will be the same for all threads createInfo.stackSize = 0; // same for all threads createInfo.allocator = nullptr; // default - for (Int32 i = 0; i < THREAD_COUNT; i++) { + for (int32_t i = 0; i < THREAD_COUNT; i++) { createInfo.arg = (void*)data; // create thread result = palCreateThread(&createInfo, &threads[i]); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } } // join the threads to main thread // joint threads does not need to be detached - for (Int32 i = 0; i < THREAD_COUNT; i++) { + for (int32_t i = 0; i < THREAD_COUNT; i++) { palJoinThread(threads[i], nullptr); } @@ -83,5 +75,5 @@ bool mutexTest() palLog(nullptr, "Final Counter: %d", data->counter); palFree(nullptr, data); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/thread_test.c b/tests/thread/thread_test.c similarity index 57% rename from tests/thread_test.c rename to tests/thread/thread_test.c index 16f0bd95..73054d4e 100644 --- a/tests/thread_test.c +++ b/tests/thread/thread_test.c @@ -1,6 +1,6 @@ -#include "pal/pal_core.h" -#include "pal/pal_thread.h" +#include "pal2/pal_thread.h" +#include "tests.h" #define THREAD_TIME 1000 #define THREAD_COUNT 4 @@ -8,7 +8,7 @@ static void* PAL_CALL worker(void* arg) { // palLog is thread safe so there should'nt be any race conditions - Int32 id = (Int32)(IntPtr)arg; + int32_t id = (int32_t)(intptr_t)arg; palLog(nullptr, "Thread %d: started", id); palSleep(THREAD_TIME * id); @@ -16,14 +16,8 @@ static void* PAL_CALL worker(void* arg) return nullptr; } -bool threadTest() +PalBool threadTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Thread Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; PalThread* threads[THREAD_COUNT]; @@ -32,30 +26,28 @@ bool threadTest() createInfo.entry = worker; // will be the same for all threads createInfo.stackSize = 0; // same for all threads createInfo.allocator = nullptr; // default - for (Int32 i = 0; i < THREAD_COUNT; i++) { - createInfo.arg = (void*)((IntPtr)i + 1); + for (int32_t i = 0; i < THREAD_COUNT; i++) { + createInfo.arg = (void*)((intptr_t)i + 1); // create thread result = palCreateThread(&createInfo, &threads[i]); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } } // join threads - for (Int32 i = 0; i < THREAD_COUNT; i++) { + for (int32_t i = 0; i < THREAD_COUNT; i++) { // we dont need the return value // joint threads does not need to be detached result = palJoinThread(threads[i], nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to join threads: %s", error); - return false; + logResult(result, "Failed to join threads"); + return PAL_FALSE; } } palLog(nullptr, "All threads finished successfully"); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/tls_test.c b/tests/thread/tls_test.c similarity index 79% rename from tests/tls_test.c rename to tests/thread/tls_test.c index fcbd3e50..87e9e73e 100644 --- a/tests/tls_test.c +++ b/tests/thread/tls_test.c @@ -1,11 +1,11 @@ -#include "pal/pal_core.h" -#include "pal/pal_thread.h" +#include "pal2/pal_thread.h" +#include "tests.h" // data every thread will have its own copy of typedef struct { const char* name; - Uint32 number; + uint32_t number; } TlsData; // thread data @@ -49,28 +49,22 @@ static void* PAL_CALL worker(void* arg) return nullptr; } -bool tlsTest() +PalBool tlsTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "TLS Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalThread* thread = nullptr; // create tls PalTLSId tlsID = palCreateTLS(TlsDestructor); if (tlsID == 0) { palLog(nullptr, "Failed to create TLS"); - return false; + return PAL_FALSE; } // allocate thread data ThreadData* threadData = palAllocate(nullptr, sizeof(ThreadData), 0); if (!threadData) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } threadData->tlsId = tlsID; @@ -83,9 +77,8 @@ bool tlsTest() PalResult result = palCreateThread(&info, &thread); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create thread: %s", error); - return false; + logResult(result, "Failed to create thread"); + return PAL_FALSE; } // join thread @@ -96,5 +89,5 @@ bool tlsTest() palDestroyTLS(tlsID); palFree(nullptr, threadData); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/attach_window_test.c b/tests/video/attach_window_test.c similarity index 75% rename from tests/attach_window_test.c rename to tests/video/attach_window_test.c index 25e0e924..ed873c0d 100644 --- a/tests/attach_window_test.c +++ b/tests/video/attach_window_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" #ifdef _WIN32 @@ -73,20 +73,21 @@ static void* createX11Window() // clang-format off s_XCreateWindow = (XCreateSimpleWindowFn)dlsym( - s_LibX, + s_LibX, "XCreateSimpleWindow"); - + s_XSync = (XSyncFn)dlsym( - s_LibX, + s_LibX, "XSync"); s_XMapRaised = (XMapRaisedFn)dlsym( - s_LibX, + s_LibX, "XMapRaised"); s_XDestroyWindow = (XDestroyWindowFn)dlsym( - s_LibX, + s_LibX, "XDestroyWindow"); + // clang-format on if (!s_XCreateWindow || !s_XSync || !s_XMapRaised || !s_XDestroyWindow) { return nullptr; @@ -102,11 +103,11 @@ static void* createX11Window() Window root = RootWindow(display, screen); Window window = s_XCreateWindow( - display, - root, - WINDOW_POSX, - WINDOW_POSX, - WINDOW_WIDTH, + display, + root, + WINDOW_POSX, + WINDOW_POSX, + WINDOW_WIDTH, WINDOW_HEIGHT, 1, BlackPixel(display, screen), @@ -119,9 +120,8 @@ static void* createX11Window() // make sure the window is mapped s_XMapRaised(display, window); s_XSync(display, False); - // clang-format on - return (void*)(UintPtr)window; + return (void*)(uintptr_t)window; #endif // __linux__ return nullptr; } @@ -182,7 +182,7 @@ static void destroyX11Window(void* windowHandle) { #ifdef __linux__ Display* display = palGetInstance(); - s_XDestroyWindow(display, (Window)(UintPtr)windowHandle); + s_XDestroyWindow(display, (Window)(uintptr_t)windowHandle); dlclose(s_LibX); // we loaded dynamically #endif } @@ -205,67 +205,51 @@ static void destroyPlatformWindow(void* windowHandle) #endif // _WIN32 } -bool attachWindowTest() +PalBool attachWindowTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Attach Window Test"); palLog(nullptr, "Press A to attach and D to detach window"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; - - // event driver - PalEventDriver* eventDriver = nullptr; + // fill the event driver create info PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = nullptr; // no callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // check for support - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS)) { - // clang-format off + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_FOREIGN_WINDOWS)) { palLog(nullptr, "Attaching and detaching foreign windows feature not supported"); - // clang-format on - palDestroyEventDriver(eventDriver); palShutdownVideo(); - return false; + return PAL_FALSE; } // we are interested in move and close events - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_MOVE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_MOVE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // we listen for key release events to attach and detach the window - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYUP, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYUP, PAL_DISPATCH_MODE_POLL); // PAL allows users create any kind of window not currently or will not // be supported by PAL and just attach the window. @@ -275,7 +259,7 @@ bool attachWindowTest() void* platformWindow = createPlatformWindow(); if (!platformWindow) { palLog(nullptr, "Failed to create platform window"); - return false; + return PAL_FALSE; } // attach the created window to PAL video system @@ -285,23 +269,16 @@ bool attachWindowTest() PalWindow* myWindow = nullptr; result = palAttachWindow(platformWindow, &myWindow); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to attach window: %s", error); - return false; + logResult(result, "Failed to attach window"); + return PAL_FALSE; } - // now that the window is attached, we can use PAL video API - // to manager it - result = palSetWindowTitle(myWindow, WINDOW_TITLE); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to attach window: %s", error); - return false; - } + // now that the window is attached, we can use PAL video API to manager it + palSetWindowTitle(myWindow, WINDOW_TITLE); - bool running = true; - bool detached = false; - Int32 counter = 0; + PalBool running = PAL_TRUE; + PalBool detached = PAL_FALSE; + int32_t counter = 0; while (running) { // update the video system to push video events palUpdateVideo(); @@ -309,44 +286,44 @@ bool attachWindowTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { + case PAL_EVENT_TYPE_WINDOW_CLOSE: { // we check if its really our attached window PalWindow* window = palUnpackPointer(event.data2); if (window == myWindow) { - running = false; + running = PAL_FALSE; } break; } - case PAL_EVENT_WINDOW_MOVE: { + case PAL_EVENT_TYPE_WINDOW_MOVE: { // this will be triggered for our attached window - Int32 x, y; // x == low, y == high + int32_t x, y; // x == low, y == high palUnpackInt32(event.data, &x, &y); palLog(nullptr, "Window Moved: (%d, %d)", x, y); break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } - case PAL_EVENT_KEYUP: { + case PAL_EVENT_TYPE_KEYUP: { // we detach the window after keyup // since if the window is detached, // we wont recieve the key up event // keycode == low, scancode == high - Uint32 keycode; + uint32_t keycode; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_D) { palDetachWindow(myWindow, nullptr); palLog(nullptr, "window detached"); palLog(nullptr, "not recieving events anymore"); - detached = true; + detached = PAL_TRUE; counter = 0; } break; @@ -363,7 +340,7 @@ bool attachWindowTest() palLog(nullptr, "window attached"); palLog(nullptr, "recieving events"); counter = 0; // reset it - detached = false; + detached = PAL_FALSE; } } @@ -371,9 +348,8 @@ bool attachWindowTest() // myPlatformWindow will be equal our platformWindow result = palDetachWindow(myWindow, &myPlatformWindow); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to attach window: %s", error); - return false; + logResult(result, "Failed to detach window"); + return PAL_FALSE; } // We need to destroy the platform window before we shutdown @@ -384,5 +360,5 @@ bool attachWindowTest() palShutdownVideo(); palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/char_event_test.c b/tests/video/char_event_test.c similarity index 60% rename from tests/char_event_test.c rename to tests/video/char_event_test.c index ea34b916..641e8db3 100644 --- a/tests/char_event_test.c +++ b/tests/video/char_event_test.c @@ -1,76 +1,65 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool charEventTest() +PalBool charEventTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Character Event Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - PalResult result; - PalWindow* window = nullptr; - PalWindowCreateInfo createInfo = {0}; - - // event driver - PalEventDriver* eventDriver = nullptr; + // fill the event driver create info PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = nullptr; // no callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.title = "Character Window"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } - // we only neeed PAL_EVENT_KEYCHAR and PAL_EVENT_WINDOW_CLOSE - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYCHAR, PAL_DISPATCH_POLL); + // we only neeed PAL_EVENT_TYPE_KEYCHAR and PAL_EVENT_TYPE_WINDOW_CLOSE + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYCHAR, PAL_DISPATCH_MODE_POLL); - bool running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -78,13 +67,13 @@ bool charEventTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYCHAR: { - Uint32 codepoint = (Uint32)event.data; + case PAL_EVENT_TYPE_KEYCHAR: { + uint32_t codepoint = (uint32_t)event.data; PalWindow* window = palUnpackPointer(event.data2); // we log the codepoint @@ -100,11 +89,11 @@ bool charEventTest() break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -123,5 +112,5 @@ bool charEventTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/cursor_test.c b/tests/video/cursor_test.c similarity index 66% rename from tests/cursor_test.c rename to tests/video/cursor_test.c index 59620be1..3e355dab 100644 --- a/tests/cursor_test.c +++ b/tests/video/cursor_test.c @@ -1,66 +1,50 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool cursorTest() +PalBool cursorTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Cursor Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalWindow* window = nullptr; - PalCursor* cursor = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalCursorCreateInfo cursorCreateInfo = {0}; - bool running = false; - - // event driver - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = nullptr; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video does not copy this, this must be valid till the // video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // check for support - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR)) { palLog(nullptr, "Seting cursors feature not supported"); palDestroyEventDriver(eventDriver); palShutdownVideo(); - return false; + return PAL_FALSE; } // simple checkerboard RGBA pixel buffer // every block contains 64 pixels - Uint8 pixels[32 * 32 * 4]; // size is 32 and we have 4 channles - for (Int32 y = 0; y < 32; ++y) { - for (Int32 x = 0; x < 32; ++x) { - Int32 i = (y * 32 + x) * 4; + uint8_t pixels[32 * 32 * 4]; // size is 32 and we have 4 channles + for (int32_t y = 0; y < 32; ++y) { + for (int32_t x = 0; x < 32; ++x) { + int32_t i = (y * 32 + x) * 4; int checker = ((x / 8) ^ (y / 8)) & 1; if (checker) { pixels[i + 0] = 255; // Red bit @@ -78,54 +62,52 @@ bool cursorTest() } // create cursor + PalCursorCreateInfo cursorCreateInfo = {0}; cursorCreateInfo.width = 32; cursorCreateInfo.height = 32; cursorCreateInfo.xHotspot = 0; cursorCreateInfo.yHotspot = 0; cursorCreateInfo.pixels = pixels; + PalCursor* cursor = nullptr; result = palCreateCursor(&cursorCreateInfo, &cursor); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window cursor: %s", error); - return false; + logResult(result, "Failed to create window cursor"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Cursor Window"; // check if we support decorated windows (title bar, close etc) - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // set the dispatch mode for window close event to recieve it - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); // polling - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // set the cursor palSetWindowCursor(window, cursor); - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -133,16 +115,16 @@ bool cursorTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -162,5 +144,5 @@ bool cursorTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/custom_decoration_test.c b/tests/video/custom_decoration_test.c similarity index 70% rename from tests/custom_decoration_test.c rename to tests/video/custom_decoration_test.c index d0965cb3..8740c531 100644 --- a/tests/custom_decoration_test.c +++ b/tests/video/custom_decoration_test.c @@ -1,8 +1,8 @@ -#if defined(__linux__) +#ifdef __linux__ #define _GNU_SOURCE #define _POSIX_C_SOURCE 200112L // for linux -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" #include @@ -42,9 +42,9 @@ typedef struct { struct wl_buffer* buffer; struct wl_surface* surface; struct wl_subsurface* subsurface; - Uint32* pixels; + uint32_t* pixels; PalEventDriver* driver; // a pointer to the event driver - Uint64 size; + uint64_t size; } WaylandDecoration; static struct wl_display* s_Display = nullptr; @@ -145,14 +145,10 @@ static inline int wlRegistryAddListener( const struct wl_registry_listener* listener, void* data) { - return s_wl_proxy_add_listener( - (struct wl_proxy*)wl_registry, - (void (**)(void))listener, - data); + return s_wl_proxy_add_listener((struct wl_proxy*)wl_registry, (void (**)(void))listener, data); } -static inline struct wl_registry* -wlDisplayGetRegistry(struct wl_display* wl_display) +static inline struct wl_registry* wlDisplayGetRegistry(struct wl_display* wl_display) { struct wl_proxy* registry; registry = s_wl_proxy_marshal_flags( @@ -166,8 +162,7 @@ wlDisplayGetRegistry(struct wl_display* wl_display) return (struct wl_registry*)registry; } -static inline struct wl_surface* -wlCompositorCreateSurface(struct wl_compositor* wl_compositor) +static inline struct wl_surface* wlCompositorCreateSurface(struct wl_compositor* wl_compositor) { struct wl_proxy* id; id = s_wl_proxy_marshal_flags( @@ -301,8 +296,7 @@ static inline void wlSurfaceDamageBuffer( height); } -static inline void -wlSubcompositorDestroy(struct wl_subcompositor* wl_subcompositor) +static inline void subcompositorDestroy(struct wl_subcompositor* wl_subcompositor) { s_wl_proxy_marshal_flags( (struct wl_proxy*)wl_subcompositor, @@ -312,7 +306,7 @@ wlSubcompositorDestroy(struct wl_subcompositor* wl_subcompositor) WL_MARSHAL_FLAG_DESTROY); } -static inline struct wl_subsurface* wlSubcompositorGetSubsurface( +static inline struct wl_subsurface* subcompositorGetSubsurface( struct wl_subcompositor* wl_subcompositor, struct wl_surface* surface, struct wl_surface* parent) @@ -331,7 +325,7 @@ static inline struct wl_subsurface* wlSubcompositorGetSubsurface( return (struct wl_subsurface*)id; } -static inline void wlSubsurfaceDestroy(struct wl_subsurface* wl_subsurface) +static inline void subsurfaceDestroy(struct wl_subsurface* wl_subsurface) { s_wl_proxy_marshal_flags( (struct wl_proxy*)wl_subsurface, @@ -341,7 +335,7 @@ static inline void wlSubsurfaceDestroy(struct wl_subsurface* wl_subsurface) WL_MARSHAL_FLAG_DESTROY); } -static inline void wlSubsurfaceSetPosition( +static inline void subsurfaceSetPosition( struct wl_subsurface* wl_subsurface, int32_t x, int32_t y) @@ -356,7 +350,7 @@ static inline void wlSubsurfaceSetPosition( y); } -static inline void wlSubsurfaceSetDesync(struct wl_subsurface* wl_subsurface) +static inline void subsurfaceSetDesync(struct wl_subsurface* wl_subsurface) { s_wl_proxy_marshal_flags( (struct wl_proxy*)wl_subsurface, @@ -380,8 +374,7 @@ static void globalHandle( s_Compositor = wlRegistryBind(registry, name, compositorInterface, 4); } else if (strcmp(interface, "wl_subcompositor") == 0) { - s_Subcompositor = - wlRegistryBind(registry, name, subCompositorInterface, 1); + s_Subcompositor = wlRegistryBind(registry, name, subCompositorInterface, 1); } else if (strcmp(interface, "wl_shm") == 0) { s_Shm = wlRegistryBind(registry, name, shmInterface, 1); @@ -448,32 +441,33 @@ static void openDisplayWayland() // clang-format off s_wl_display_connect = (wl_display_connect_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_connect"); s_wl_display_disconnect = (wl_display_disconnect_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_disconnect"); s_wl_display_roundtrip = (wl_display_roundtrip_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_roundtrip"); s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_marshal_flags"); s_wl_proxy_get_version = (wl_proxy_get_version_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_get_version"); s_wl_proxy_add_listener = (wl_proxy_add_listener_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_add_listener"); s_wl_proxy_destroy = (wl_proxy_destroy_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_destroy"); + // clang-format on registryInterface = dlsym(s_LibWayland, "wl_registry_interface"); seatInterface = dlsym(s_LibWayland, "wl_seat_interface"); @@ -502,17 +496,17 @@ static void openDisplayWayland() static void closeDisplayWayland() { if (s_Compositor) { - s_wl_proxy_destroy((struct wl_proxy *)s_Compositor); - s_wl_proxy_destroy((struct wl_proxy *)s_Shm); - s_wl_proxy_destroy((struct wl_proxy *)s_Seat); - wlSubcompositorDestroy(s_Subcompositor); + s_wl_proxy_destroy((struct wl_proxy*)s_Compositor); + s_wl_proxy_destroy((struct wl_proxy*)s_Shm); + s_wl_proxy_destroy((struct wl_proxy*)s_Seat); + subcompositorDestroy(s_Subcompositor); } s_wl_display_disconnect((struct wl_display*)s_Display); dlclose(s_LibWayland); } -static int createShmFile(Uint64 size) +static int createShmFile(uint64_t size) { char template[] = "/tmp/pal-shm-XXXXXX"; int fd = mkstemp(template); @@ -532,11 +526,11 @@ static struct wl_buffer* createShmBuffer( int width, int height, int* outFd, - Uint64* outSize, - Uint32** outPixels) + uint64_t* outSize, + uint32_t** outPixels) { int stride = width * 4; - Uint64 size = stride * height; + uint64_t size = stride * height; struct wl_buffer* buffer = nullptr; struct wl_shm_pool* pool = nullptr; void* data = nullptr; @@ -560,23 +554,23 @@ static struct wl_buffer* createShmBuffer( if (!buffer) { return nullptr; } - + wlShmPoolDestroy(pool); - *outPixels = (Uint32*)data; + *outPixels = (uint32_t*)data; *outFd = fd; *outSize = size; return buffer; } void fillRect( - uint32_t *pixels, - int stride, - int x, - int y, - int w, - int h, - uint32_t color) + uint32_t* pixels, + int stride, + int x, + int y, + int w, + int h, + uint32_t color) { for (int j = 0; j < h; j++) { for (int i = 0; i < w; i++) { @@ -588,81 +582,82 @@ void fillRect( // a simple 8x8 bitmap font // exchange with your font // Each byte = one row of 8 pixels, MSB = leftmost pixel -static const Uint8 s_FontBasic[128][8] = { - ['A'] = {0x18,0x24,0x42,0x42,0x7E,0x42,0x42,0x42}, - ['B'] = {0x7C,0x42,0x42,0x7C,0x42,0x42,0x42,0x7C}, - ['C'] = {0x3C,0x42,0x40,0x40,0x40,0x40,0x42,0x3C}, - ['D'] = {0x78,0x44,0x42,0x42,0x42,0x42,0x44,0x78}, - ['E'] = {0x7E,0x40,0x40,0x7C,0x40,0x40,0x40,0x7E}, - ['F'] = {0x7E,0x40,0x40,0x7C,0x40,0x40,0x40,0x40}, - ['G'] = {0x3C,0x42,0x40,0x40,0x4E,0x42,0x42,0x3C}, - ['H'] = {0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x42}, - ['I'] = {0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x3E}, - ['J'] = {0x1E,0x04,0x04,0x04,0x04,0x44,0x44,0x38}, - ['K'] = {0x42,0x44,0x48,0x70,0x48,0x44,0x42,0x42}, - ['L'] = {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7E}, - ['M'] = {0x42,0x66,0x5A,0x5A,0x42,0x42,0x42,0x42}, - ['N'] = {0x42,0x62,0x52,0x4A,0x46,0x42,0x42,0x42}, - ['O'] = {0x3C,0x42,0x42,0x42,0x42,0x42,0x42,0x3C}, - ['P'] = {0x7C,0x42,0x42,0x7C,0x40,0x40,0x40,0x40}, - ['Q'] = {0x3C,0x42,0x42,0x42,0x42,0x4A,0x44,0x3A}, - ['R'] = {0x7C,0x42,0x42,0x7C,0x48,0x44,0x42,0x42}, - ['S'] = {0x3C,0x42,0x40,0x3C,0x02,0x02,0x42,0x3C}, - ['T'] = {0x7F,0x49,0x08,0x08,0x08,0x08,0x08,0x08}, - ['U'] = {0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x3C}, - ['V'] = {0x42,0x42,0x42,0x42,0x42,0x24,0x24,0x18}, - ['W'] = {0x42,0x42,0x42,0x5A,0x5A,0x5A,0x66,0x42}, - ['X'] = {0x42,0x42,0x24,0x18,0x18,0x24,0x42,0x42}, - ['Y'] = {0x42,0x42,0x24,0x18,0x08,0x08,0x08,0x08}, - ['Z'] = {0x7E,0x02,0x04,0x08,0x10,0x20,0x40,0x7E}, - - ['a'] = {0x00,0x00,0x3C,0x02,0x3E,0x42,0x46,0x3A}, - ['b'] = {0x40,0x40,0x5C,0x62,0x42,0x42,0x62,0x5C}, - ['c'] = {0x00,0x00,0x3C,0x42,0x40,0x40,0x42,0x3C}, - ['d'] = {0x02,0x02,0x3A,0x46,0x42,0x42,0x46,0x3A}, - ['e'] = {0x00,0x00,0x3C,0x42,0x7E,0x40,0x42,0x3C}, - ['f'] = {0x0C,0x12,0x10,0x3C,0x10,0x10,0x10,0x10}, - ['g'] = {0x00,0x00,0x3A,0x46,0x46,0x3E,0x02,0x3C}, - ['h'] = {0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x42}, - ['i'] = {0x08,0x00,0x18,0x08,0x08,0x08,0x08,0x1C}, - ['j'] = {0x04,0x00,0x0C,0x04,0x04,0x04,0x44,0x38}, - ['k'] = {0x40,0x40,0x44,0x48,0x70,0x48,0x44,0x42}, - ['l'] = {0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x1C}, - ['m'] = {0x00,0x00,0x6C,0x52,0x52,0x42,0x42,0x42}, - ['n'] = {0x00,0x00,0x5C,0x62,0x42,0x42,0x42,0x42}, - ['o'] = {0x00,0x00,0x3C,0x42,0x42,0x42,0x42,0x3C}, - ['p'] = {0x00,0x00,0x5C,0x62,0x42,0x62,0x40,0x40}, - ['q'] = {0x00,0x00,0x3A,0x46,0x42,0x46,0x02,0x02}, - ['r'] = {0x00,0x00,0x5C,0x62,0x40,0x40,0x40,0x40}, - ['s'] = {0x00,0x00,0x3E,0x40,0x3C,0x02,0x42,0x3C}, - ['t'] = {0x10,0x10,0x3C,0x10,0x10,0x12,0x0C,0x00}, - ['u'] = {0x00,0x00,0x42,0x42,0x42,0x42,0x46,0x3A}, - ['v'] = {0x00,0x00,0x42,0x42,0x42,0x24,0x24,0x18}, - ['w'] = {0x00,0x00,0x42,0x42,0x42,0x52,0x52,0x2C}, - ['x'] = {0x00,0x00,0x42,0x24,0x18,0x18,0x24,0x42}, - ['y'] = {0x00,0x00,0x42,0x42,0x46,0x3A,0x02,0x3C}, - ['z'] = {0x00,0x00,0x7E,0x04,0x08,0x10,0x20,0x7E}, +static const uint8_t s_FontBasic[128][8] = { + ['A'] = {0x18, 0x24, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42}, + ['B'] = {0x7C, 0x42, 0x42, 0x7C, 0x42, 0x42, 0x42, 0x7C}, + ['C'] = {0x3C, 0x42, 0x40, 0x40, 0x40, 0x40, 0x42, 0x3C}, + ['D'] = {0x78, 0x44, 0x42, 0x42, 0x42, 0x42, 0x44, 0x78}, + ['E'] = {0x7E, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x7E}, + ['F'] = {0x7E, 0x40, 0x40, 0x7C, 0x40, 0x40, 0x40, 0x40}, + ['G'] = {0x3C, 0x42, 0x40, 0x40, 0x4E, 0x42, 0x42, 0x3C}, + ['H'] = {0x42, 0x42, 0x42, 0x7E, 0x42, 0x42, 0x42, 0x42}, + ['I'] = {0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E}, + ['J'] = {0x1E, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x38}, + ['K'] = {0x42, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x42}, + ['L'] = {0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7E}, + ['M'] = {0x42, 0x66, 0x5A, 0x5A, 0x42, 0x42, 0x42, 0x42}, + ['N'] = {0x42, 0x62, 0x52, 0x4A, 0x46, 0x42, 0x42, 0x42}, + ['O'] = {0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C}, + ['P'] = {0x7C, 0x42, 0x42, 0x7C, 0x40, 0x40, 0x40, 0x40}, + ['Q'] = {0x3C, 0x42, 0x42, 0x42, 0x42, 0x4A, 0x44, 0x3A}, + ['R'] = {0x7C, 0x42, 0x42, 0x7C, 0x48, 0x44, 0x42, 0x42}, + ['S'] = {0x3C, 0x42, 0x40, 0x3C, 0x02, 0x02, 0x42, 0x3C}, + ['T'] = {0x7F, 0x49, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, + ['U'] = {0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C}, + ['V'] = {0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x24, 0x18}, + ['W'] = {0x42, 0x42, 0x42, 0x5A, 0x5A, 0x5A, 0x66, 0x42}, + ['X'] = {0x42, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x42}, + ['Y'] = {0x42, 0x42, 0x24, 0x18, 0x08, 0x08, 0x08, 0x08}, + ['Z'] = {0x7E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7E}, + + ['a'] = {0x00, 0x00, 0x3C, 0x02, 0x3E, 0x42, 0x46, 0x3A}, + ['b'] = {0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x62, 0x5C}, + ['c'] = {0x00, 0x00, 0x3C, 0x42, 0x40, 0x40, 0x42, 0x3C}, + ['d'] = {0x02, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x46, 0x3A}, + ['e'] = {0x00, 0x00, 0x3C, 0x42, 0x7E, 0x40, 0x42, 0x3C}, + ['f'] = {0x0C, 0x12, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x10}, + ['g'] = {0x00, 0x00, 0x3A, 0x46, 0x46, 0x3E, 0x02, 0x3C}, + ['h'] = {0x40, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42}, + ['i'] = {0x08, 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x1C}, + ['j'] = {0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x44, 0x38}, + ['k'] = {0x40, 0x40, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42}, + ['l'] = {0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C}, + ['m'] = {0x00, 0x00, 0x6C, 0x52, 0x52, 0x42, 0x42, 0x42}, + ['n'] = {0x00, 0x00, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x42}, + ['o'] = {0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C}, + ['p'] = {0x00, 0x00, 0x5C, 0x62, 0x42, 0x62, 0x40, 0x40}, + ['q'] = {0x00, 0x00, 0x3A, 0x46, 0x42, 0x46, 0x02, 0x02}, + ['r'] = {0x00, 0x00, 0x5C, 0x62, 0x40, 0x40, 0x40, 0x40}, + ['s'] = {0x00, 0x00, 0x3E, 0x40, 0x3C, 0x02, 0x42, 0x3C}, + ['t'] = {0x10, 0x10, 0x3C, 0x10, 0x10, 0x12, 0x0C, 0x00}, + ['u'] = {0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3A}, + ['v'] = {0x00, 0x00, 0x42, 0x42, 0x42, 0x24, 0x24, 0x18}, + ['w'] = {0x00, 0x00, 0x42, 0x42, 0x42, 0x52, 0x52, 0x2C}, + ['x'] = {0x00, 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42}, + ['y'] = {0x00, 0x00, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x3C}, + ['z'] = {0x00, 0x00, 0x7E, 0x04, 0x08, 0x10, 0x20, 0x7E}, }; void drawCharacter( - uint32_t *pixels, - int stride, - int x, - int y, - char c, - uint32_t color) + uint32_t* pixels, + int stride, + int x, + int y, + char c, + uint32_t color) { - if (c < 0 || c > 127) return; // not in out font + if (c < 0 || c > 127) + return; // not in out font - const uint8_t *bitmap = s_FontBasic[(int)c]; + const uint8_t* bitmap = s_FontBasic[(int)c]; for (int row = 0; row < 8; row++) { uint8_t bits = bitmap[row]; for (int col = 0; col < 8; col++) { - if (bits & (1 << (7-col))) { + if (bits & (1 << (7 - col))) { int px = x + col; int py = y + row; if (px < stride && py < TITLEBAR_HEIGHT) { - pixels[py*stride + px] = color; + pixels[py * stride + px] = color; } } } @@ -670,12 +665,12 @@ void drawCharacter( } void drawText( - uint32_t *pixels, - int stride, - int x, - int y, - const char *text, - uint32_t color) + uint32_t* pixels, + int stride, + int x, + int y, + const char* text, + uint32_t color) { while (*text) { drawCharacter(pixels, stride, x, y, *text, color); @@ -684,7 +679,7 @@ void drawText( } } -static PalWindowHandleInfoEx s_WinHandle; +static PalWindowHandleInfo s_WinHandle; static void createDecoration() { @@ -694,11 +689,10 @@ static void createDecoration() return; } - s_Decoration.subsurface = wlSubcompositorGetSubsurface( + s_Decoration.subsurface = subcompositorGetSubsurface( s_Subcompositor, s_Decoration.surface, - (struct wl_surface*)s_WinHandle.nativeWindow - ); + (struct wl_surface*)s_WinHandle.nativeWindow); if (!s_Decoration.subsurface) { palLog(nullptr, "Failed to create wayland subsurface"); @@ -706,8 +700,8 @@ static void createDecoration() } // make it soo that the decoration updates independently of the window - wlSubsurfaceSetDesync(s_Decoration.subsurface); - wlSubsurfaceSetPosition(s_Decoration.subsurface, 0, 0); + subsurfaceSetDesync(s_Decoration.subsurface); + subsurfaceSetPosition(s_Decoration.subsurface, 0, 0); // create decoration shm buffer int width = 640; @@ -725,44 +719,37 @@ static void createDecoration() // write pixels // title bar color (dark grey) - fillRect( - s_Decoration.pixels, - width, - 0, - 0, - width, - TITLEBAR_HEIGHT, - 0x002F3030); + fillRect(s_Decoration.pixels, width, 0, 0, width, TITLEBAR_HEIGHT, 0x002F3030); // Close button // this is just a rectangle for this simple example // to show CSD works with PAL fillRect( - s_Decoration.pixels, - width, - width - BUTTON_SIZE, - BUTTON_POSY, - BUTTON_SIZE, + s_Decoration.pixels, + width, + width - BUTTON_SIZE, + BUTTON_POSY, + BUTTON_SIZE, TITLEBAR_HEIGHT / 2, // half of the size of the title bar 0x00AA3333); // Maximize button fillRect( - s_Decoration.pixels, - width, - width - (BUTTON_OFFSET + BUTTON_SIZE), + s_Decoration.pixels, + width, + width - (BUTTON_OFFSET + BUTTON_SIZE), BUTTON_POSY, - BUTTON_SIZE, + BUTTON_SIZE, TITLEBAR_HEIGHT / 2, // half of the size of the title bar 0x0033AA33); // Minimize button fillRect( s_Decoration.pixels, - width, - width - (BUTTON_OFFSET * 2 + BUTTON_SIZE), + width, + width - (BUTTON_OFFSET * 2 + BUTTON_SIZE), BUTTON_POSY, - BUTTON_SIZE, + BUTTON_SIZE, TITLEBAR_HEIGHT / 2, // half of the size of the title bar 0x0033AAAA); @@ -779,13 +766,7 @@ static void createDecoration() int x = (width - textWidth) / 2; int y = (TITLEBAR_HEIGHT - 8) / 2; - drawText( - s_Decoration.pixels, - width, - x, - y, - WINDOW_TITLE, - 0x00FFFFFF); + drawText(s_Decoration.pixels, width, x, y, WINDOW_TITLE, 0x00FFFFFF); // set opaque regions for optimazation // we wont do this in this example @@ -795,7 +776,7 @@ static void createDecoration() static void destroyDecoration() { - wlSubsurfaceDestroy(s_Decoration.subsurface); + subsurfaceDestroy(s_Decoration.subsurface); wlSurfaceDestroy(s_Decoration.surface); wlBufferDestroy(s_Decoration.buffer); munmap((void*)s_Decoration.pixels, s_Decoration.size); @@ -806,8 +787,8 @@ static void PAL_CALL onEvent( void* userData, const PalEvent* event) { - if (event->type == PAL_EVENT_MOUSE_BUTTONDOWN) { - Uint32 button, serial; + if (event->type == PAL_EVENT_TYPE_MOUSE_BUTTONDOWN) { + uint32_t button, serial; palUnpackUint32(event->data, &button, &serial); if (button == PAL_MOUSE_BUTTON_LEFT) { @@ -822,136 +803,117 @@ static void PAL_CALL onEvent( xdgToplevelMove(s_WinHandle.nativeHandle2, s_Seat, serial); // Optionally check for another click and maximize the window - // maybe set a bool or query mouse button state + // maybe set a PalBool or query mouse button state // we will skip it for this example } // we skip maximize and minimize button for simplicity // we just deal with the close button int buttonX = 640 - BUTTON_SIZE; - if (x >= buttonX && - x < buttonX + BUTTON_SIZE && - y >= BUTTON_POSY && + if (x >= buttonX && x < buttonX + BUTTON_SIZE && y >= BUTTON_POSY && y < BUTTON_POSY + BUTTON_SIZE) { // inside close button // trigger a window close event PalEvent event = {0}; event.data2 = palPackPointer(s_WinHandle.nativeWindow); - event.type = PAL_EVENT_WINDOW_CLOSE; + event.type = PAL_EVENT_TYPE_WINDOW_CLOSE; palPushEvent(s_Decoration.driver, &event); } - } + } - } else if (event->type == PAL_EVENT_MOUSE_MOVE) { - Int32 x, y; + } else if (event->type == PAL_EVENT_TYPE_MOUSE_MOVE) { + int32_t x, y; palUnpackInt32(event->data, &x, &y); s_Decoration.mouseX = x; s_Decoration.mouseY = y; - } else if (event->type == PAL_EVENT_MONITOR_DPI_CHANGED) { + } else if (event->type == PAL_EVENT_TYPE_MONITOR_DPI_CHANGED) { palLog(nullptr, "Monitor DPI: %d", event->data); } } -bool customDecorationTest() -{ - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Custom Decoration Test"); - palLog(nullptr, "Press Escape or click close button to close Test"); - - palLog(nullptr, - "This only implements close and window movement for simplicity"); - - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); +#else +#include "pal2/pal_core.h" +#endif // __linux__ +PalBool customDecorationTest() +{ +#ifdef __linux__ openDisplayWayland(); if (!s_Display) { // not on wayland palLog(nullptr, "Not on wayland platform"); - return false; + return PAL_FALSE; } - PalResult result; + palLog(nullptr, "Press Escape or click close button to close Test"); + palLog(nullptr, "This only implements close and window movement for simplicity"); - // create an event driver - PalEventDriver* eventDriver = nullptr; + // fill the event driver create info PalEventDriverCreateInfo eventDriverCreateInfo = {0}; - eventDriverCreateInfo.callback = onEvent; + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = onEvent; // no callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + // create the event driver + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); palSetEventDispatchMode( eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_DECORATION_MODE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_WINDOW_DECORATION_MODE, + PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); - // we use PAL_DISPATCH_CALLBACK for the mouse button to get + // we use PAL_DISPATCH_MODE_CALLBACK for the mouse button to get // real time events which we then use for moving and resizing palSetEventDispatchMode( eventDriver, - PAL_EVENT_MOUSE_BUTTONDOWN, - PAL_DISPATCH_CALLBACK); - - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_MOUSE_MOVE, - PAL_DISPATCH_CALLBACK); - + PAL_EVENT_TYPE_MOUSE_BUTTONDOWN, + PAL_DISPATCH_MODE_CALLBACK); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_MOUSE_MOVE, PAL_DISPATCH_MODE_CALLBACK); palSetEventDispatchMode( eventDriver, - PAL_EVENT_MONITOR_DPI_CHANGED, - PAL_DISPATCH_CALLBACK); - - // tell the video system to use out instance rather - // than creating a new one - palSetPreferredInstance((void*)s_Display); + PAL_EVENT_TYPE_MONITOR_DPI_CHANGED, + PAL_DISPATCH_MODE_CALLBACK); // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + // tell the video system to use out instance rather than creating a new one + result = palInitVideo(nullptr, eventDriver, (void*)s_Display); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } PalWindow* window = nullptr; PalWindowCreateInfo createInfo = {0}; createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_BORDERLESS; createInfo.title = WINDOW_TITLE; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // get native handles - s_WinHandle = palGetWindowHandleInfoEx(window); + palGetWindowHandleInfo(window, &s_WinHandle); s_Decoration.driver = eventDriver; createDecoration(); - bool running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -959,21 +921,21 @@ bool customDecorationTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } - case PAL_EVENT_WINDOW_DECORATION_MODE: { + case PAL_EVENT_TYPE_WINDOW_DECORATION_MODE: { if (event.data == PAL_DECORATION_MODE_CLIENT_SIDE) { palLog(nullptr, "Window Decoration Mode: Client Side"); @@ -984,7 +946,6 @@ bool customDecorationTest() } } } - } // destroy decoration @@ -1001,14 +962,10 @@ bool customDecorationTest() closeDisplayWayland(); - return true; -} + return PAL_TRUE; -#else -#include "tests.h" -bool customDecorationTest() -{ - return false; +#else + palLog(nullptr, "Custom decoration not supported"); + return PAL_FALSE; +#endif // __linux__ } - -#endif // __linux__ \ No newline at end of file diff --git a/tests/icon_test.c b/tests/video/icon_test.c similarity index 62% rename from tests/icon_test.c rename to tests/video/icon_test.c index 46829268..e06fd194 100644 --- a/tests/icon_test.c +++ b/tests/video/icon_test.c @@ -1,66 +1,50 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool iconTest() +PalBool iconTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Icon Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalWindow* window = nullptr; - PalIcon* icon = nullptr; - PalWindowCreateInfo createInfo = {0}; - PalIconCreateInfo iconCreateInfo = {0}; - bool running = false; - - // event driver - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = nullptr; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video does not copy this, this must be valid till the // video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // check for support - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_ICON)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_WINDOW_SET_ICON)) { palLog(nullptr, "Setting icons feature not supported"); palDestroyEventDriver(eventDriver); palShutdownVideo(); - return false; + return PAL_FALSE; } // simple checkerboard RGBA pixel buffer // every block contains 64 pixels - Uint8 pixels[32 * 32 * 4]; // size is 32 and we have 4 channles - for (Int32 y = 0; y < 32; ++y) { - for (Int32 x = 0; x < 32; ++x) { - Int32 i = (y * 32 + x) * 4; + uint8_t pixels[32 * 32 * 4]; // size is 32 and we have 4 channles + for (int32_t y = 0; y < 32; ++y) { + for (int32_t x = 0; x < 32; ++x) { + int32_t i = (y * 32 + x) * 4; int checker = ((x / 8) ^ (y / 8)) & 1; if (checker) { pixels[i + 0] = 255; // Red bit @@ -78,50 +62,43 @@ bool iconTest() } // create icon + PalIconCreateInfo iconCreateInfo = {0}; iconCreateInfo.width = 32; iconCreateInfo.height = 32; iconCreateInfo.pixels = pixels; // can be loaded from an .ico file + PalIcon* icon = nullptr; result = palCreateIcon(&iconCreateInfo, &icon); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window icon: %s", error); - return false; + logResult(result, "Failed to create window icon"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Icon Window"; // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // set the dispatch mode for window close event to recieve it - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); // polling - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // set the icon - result = palSetWindowIcon(window, icon); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set window icon: %s", error); - return false; - } + palSetWindowIcon(window, icon); - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -129,16 +106,16 @@ bool iconTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -158,5 +135,5 @@ bool iconTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/input_window_test.c b/tests/video/input_window_test.c similarity index 78% rename from tests/input_window_test.c rename to tests/video/input_window_test.c index 0abbee3e..bc84ad3b 100644 --- a/tests/input_window_test.c +++ b/tests/video/input_window_test.c @@ -1,10 +1,10 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" #define DISPATCH_MODE_POLL 0 -static const char* s_KeyNames[PAL_KEYCODE_MAX] = { +static const char* s_KeyNames[PAL_KEYCODE_COUNT] = { [PAL_KEYCODE_UNKNOWN] = "Unknown", @@ -131,7 +131,7 @@ static const char* s_KeyNames[PAL_KEYCODE_MAX] = { [PAL_KEYCODE_RSUPER] = "RightSuper", }; -static const char* s_ScancodeNames[PAL_SCANCODE_MAX] = { +static const char* s_ScancodeNames[PAL_SCANCODE_COUNT] = { [PAL_SCANCODE_UNKNOWN] = "Unknown", @@ -256,7 +256,7 @@ static const char* s_ScancodeNames[PAL_SCANCODE_MAX] = { [PAL_SCANCODE_LSUPER] = "LeftSuper", [PAL_SCANCODE_RSUPER] = "RightSuper"}; -static const char* s_MouseButtonNames[PAL_MOUSE_BUTTON_MAX] = { +static const char* s_MouseButtonNames[PAL_MOUSE_BUTTON_COUNT] = { [PAL_MOUSE_BUTTON_UNKNOWN] = "Unknown", @@ -272,67 +272,52 @@ static const char* dispatchString = "Poll Mode"; static const char* dispatchString = "Callback Mode"; #endif // DISPATCH_MODE_POLL -static bool s_Running = false; +static PalBool s_Running = PAL_FALSE; // inline helpers static inline void onKeydown(const PalEvent* event) { - Uint32 keycode, scancode; // keycode == low, scancode == high + uint32_t keycode, scancode; // keycode == low, scancode == high palUnpackUint32(event->data, &keycode, &scancode); PalWindow* window = palUnpackPointer(event->data2); // get keycode and scancode name const char* keyName = s_KeyNames[keycode]; const char* scancodeName = s_ScancodeNames[scancode]; - palLog( - nullptr, - "%s: Key pressed: (%s, %s)", - dispatchString, - keyName, - scancodeName); + palLog(nullptr, "%s: Key pressed: (%s, %s)", dispatchString, keyName, scancodeName); if (keycode == PAL_KEYCODE_ESCAPE) { - s_Running = false; + s_Running = PAL_FALSE; } } static inline void onKeyrepeat(const PalEvent* event) { - Uint32 keycode, scancode; // keycode == low, scancode == high + uint32_t keycode, scancode; // keycode == low, scancode == high palUnpackUint32(event->data, &keycode, &scancode); PalWindow* window = palUnpackPointer(event->data2); // get keycode and scancode name const char* keyName = s_KeyNames[keycode]; const char* scancodeName = s_ScancodeNames[scancode]; - palLog( - nullptr, - "%s: Key repeat: (%s, %s)", - dispatchString, - keyName, - scancodeName); + palLog(nullptr, "%s: Key repeat: (%s, %s)", dispatchString, keyName, scancodeName); } static inline void onKeyup(const PalEvent* event) { - Uint32 keycode, scancode; // keycode == low, scancode == high + uint32_t keycode, scancode; // keycode == low, scancode == high palUnpackUint32(event->data, &keycode, &scancode); PalWindow* window = palUnpackPointer(event->data2); // get keycode and scancode name const char* keyName = s_KeyNames[keycode]; const char* scancodeName = s_ScancodeNames[scancode]; - palLog( - nullptr, - "%s: Key released: (%s, %s)", - dispatchString, - keyName, - scancodeName); + palLog(nullptr, "%s: Key released: (%s, %s)", dispatchString, keyName, scancodeName); } static inline void onMouseButtondown(const PalEvent* event) { - Uint32 button, serial; // button == low, serial == high + uint32_t button, serial; // button == low, serial == high palUnpackUint32(event->data, &button, &serial); PalWindow* window = palUnpackPointer(event->data2); @@ -343,7 +328,7 @@ static inline void onMouseButtondown(const PalEvent* event) static inline void onMouseButtonup(const PalEvent* event) { - Uint32 button, serial; // button == low, serial == high + uint32_t button, serial; // button == low, serial == high palUnpackUint32(event->data, &button, &serial); PalWindow* window = palUnpackPointer(event->data2); @@ -354,7 +339,7 @@ static inline void onMouseButtonup(const PalEvent* event) static inline void onMouseMove(const PalEvent* event) { - Int32 x, y; // x == low, y == high + int32_t x, y; // x == low, y == high palUnpackInt32(event->data, &x, &y); PalWindow* window = palUnpackPointer(event->data2); palLog(nullptr, "%s: Mouse Moved: (%d, %d)", dispatchString, x, y); @@ -362,146 +347,119 @@ static inline void onMouseMove(const PalEvent* event) static inline void onMouseDelta(const PalEvent* event) { - Int32 dx, dy; // dx == low, dy == high - palUnpackInt32(event->data, &dx, &dy); + float dx, dy; // dx == low, dy == high + palUnpackFloat(event->data, &dx, &dy); PalWindow* window = palUnpackPointer(event->data2); - palLog(nullptr, "%s: Mouse Delta: (%d, %d)", dispatchString, dx, dy); + palLog(nullptr, "%s: Mouse Delta: (%.2f, %.2f)", dispatchString, dx, dy); } static inline void onMouseWheel(const PalEvent* event) { - Int32 dx, dy; // dx == low, dy == high - palUnpackInt32(event->data, &dx, &dy); - - // get the raw wheel delta (float) - float fdx, fdy; - palGetRawMouseWheelDelta(&fdx, &fdy); - + float dx, dy; // dx == low, dy == high + palUnpackFloat(event->data, &dx, &dy); PalWindow* window = palUnpackPointer(event->data2); - palLog(nullptr, "%s: Mouse Wheel: (%d, %d)", dispatchString, dx, dy); - palLog( - nullptr, - "%s: Mouse Wheel Raw: (%.2f, %.2f)", - dispatchString, - fdx, - fdy); + palLog(nullptr, "%s: Mouse Wheel: (%.2f, %.2f)", dispatchString, dx, dy); } static void PAL_CALL onEvent( void* userData, const PalEvent* event) { - if (event->type == PAL_EVENT_KEYDOWN) { + if (event->type == PAL_EVENT_TYPE_KEYDOWN) { onKeydown(event); - } else if (event->type == PAL_EVENT_KEYREPEAT) { + } else if (event->type == PAL_EVENT_TYPE_KEYREPEAT) { onKeyrepeat(event); - } else if (event->type == PAL_EVENT_KEYUP) { + } else if (event->type == PAL_EVENT_TYPE_KEYUP) { onKeyup(event); - } else if (event->type == PAL_EVENT_MOUSE_BUTTONDOWN) { + } else if (event->type == PAL_EVENT_TYPE_MOUSE_BUTTONDOWN) { onMouseButtondown(event); - } else if (event->type == PAL_EVENT_MOUSE_BUTTONUP) { + } else if (event->type == PAL_EVENT_TYPE_MOUSE_BUTTONUP) { onMouseButtonup(event); - } else if (event->type == PAL_EVENT_MOUSE_MOVE) { + } else if (event->type == PAL_EVENT_TYPE_MOUSE_MOVE) { onMouseMove(event); - } else if (event->type == PAL_EVENT_MOUSE_DELTA) { + } else if (event->type == PAL_EVENT_TYPE_MOUSE_DELTA) { onMouseDelta(event); - } else if (event->type == PAL_EVENT_MOUSE_WHEEL) { + } else if (event->type == PAL_EVENT_TYPE_MOUSE_WHEEL) { onMouseWheel(event); } } -bool inputWindowTest() +PalBool inputWindowTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Input Window Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalWindow* window = nullptr; - PalWindowCreateInfo createInfo = {0}; - bool running = false; - - // event driver - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = onEvent; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Input Window"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); - PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; + PalDispatchMode dispatchMode = PAL_DISPATCH_MODE_NONE; #if DISPATCH_MODE_POLL - dispatchMode = PAL_DISPATCH_POLL; + dispatchMode = PAL_DISPATCH_MODE_POLL; #else - dispatchMode = PAL_DISPATCH_CALLBACK; + dispatchMode = PAL_DISPATCH_MODE_CALLBACK; #endif // DISPATCH_MODE_POLL // set dispatch mode for all events. - for (Uint32 e = PAL_EVENT_KEYDOWN; e < PAL_EVENT_USER; e++) { + for (uint32_t e = PAL_EVENT_TYPE_KEYDOWN; e < PAL_EVENT_TYPE_USER; e++) { palSetEventDispatchMode(eventDriver, e, dispatchMode); } - s_Running = true; + s_Running = PAL_TRUE; while (s_Running) { // update the video system to push video events palUpdateVideo(); @@ -509,47 +467,47 @@ bool inputWindowTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - s_Running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + s_Running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { onKeydown(&event); break; } - case PAL_EVENT_KEYREPEAT: { + case PAL_EVENT_TYPE_KEYREPEAT: { onKeyrepeat(&event); break; } - case PAL_EVENT_KEYUP: { + case PAL_EVENT_TYPE_KEYUP: { onKeyup(&event); break; } - case PAL_EVENT_MOUSE_BUTTONDOWN: { + case PAL_EVENT_TYPE_MOUSE_BUTTONDOWN: { onMouseButtondown(&event); break; } - case PAL_EVENT_MOUSE_BUTTONUP: { + case PAL_EVENT_TYPE_MOUSE_BUTTONUP: { onMouseButtonup(&event); break; } - case PAL_EVENT_MOUSE_MOVE: { + case PAL_EVENT_TYPE_MOUSE_MOVE: { onMouseMove(&event); break; } - case PAL_EVENT_MOUSE_DELTA: { + case PAL_EVENT_TYPE_MOUSE_DELTA: { onMouseDelta(&event); break; } - case PAL_EVENT_MOUSE_WHEEL: { + case PAL_EVENT_TYPE_MOUSE_WHEEL: { onMouseWheel(&event); break; } @@ -565,5 +523,5 @@ bool inputWindowTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/monitor_mode_test.c b/tests/video/monitor_mode_test.c similarity index 54% rename from tests/monitor_mode_test.c rename to tests/video/monitor_mode_test.c index e67db812..5ab02744 100644 --- a/tests/monitor_mode_test.c +++ b/tests/video/monitor_mode_test.c @@ -1,39 +1,28 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool monitorModeTest() +PalBool monitorModeTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Monitor Mode Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalMonitorInfo info; - Int32 count = 0; - Int32 modeCount = 0; - // initialize the video system - PalResult result = palInitVideo(nullptr, nullptr); + PalResult result = palInitVideo(nullptr, nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // get the number of connected monitors + uint32_t count = 0; result = palEnumerateMonitors(&count, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors: %s", error); - return false; + logResult(result, "Failed to get query monitors"); + return PAL_FALSE; } // if count == 0, we fail if (count == 0) { palLog(nullptr, "No monitor connected"); - return false; + return PAL_FALSE; } palLog(nullptr, "Monitor Count: %d", count); @@ -43,38 +32,32 @@ bool monitorModeTest() monitors = palAllocate(nullptr, sizeof(PalMonitor*) * count, 0); if (!monitors) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // get the handle of the connected monitors result = palEnumerateMonitors(&count, monitors); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors: %s", error); - return false; + logResult(result, "Failed to get query monitors"); + return PAL_FALSE; } // get monitor info for every monitor and log the information - for (Int32 i = 0; i < count; i++) { + PalMonitorInfo info = {0}; + for (int32_t i = 0; i < count; i++) { PalMonitor* monitor = monitors[i]; - result = palGetMonitorInfo(monitor, &info); + palGetMonitorInfo(monitor, &info); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get monitor info: %s", error); - palFree(nullptr, monitors); - return false; + logResult(result, "Failed to get monitor info"); + return PAL_FALSE; } // log monitor name palLog(nullptr, "Monitor Name: %s", info.name); // get number of monitor modes - result = palEnumerateMonitorModes(monitor, &modeCount, nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitor modes: %s", error); - return false; - } + uint32_t modeCount = 0; + palEnumerateMonitorModes(monitor, &modeCount, nullptr); palLog(nullptr, "Monitor Mode Count: %d", modeCount); // allocate an array of monitor modes or use a fixed array @@ -84,18 +67,13 @@ bool monitorModeTest() if (!modes) { palLog(nullptr, "Failed to allocate memory"); palFree(nullptr, monitors); - return false; + return PAL_FALSE; } // get the monitor modes - result = palEnumerateMonitorModes(monitor, &modeCount, modes); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitor modes: %s", error); - return false; - } + palEnumerateMonitorModes(monitor, &modeCount, modes); - for (Int32 i = 0; i < modeCount; i++) { + for (int32_t i = 0; i < modeCount; i++) { // log monitor mode PalMonitorMode* mode = &modes[i]; palLog(nullptr, " Mode Index: %d", i); @@ -109,12 +87,7 @@ bool monitorModeTest() // get current monitor mode and log PalMonitorMode current; - result = palGetCurrentMonitorMode(monitor, ¤t); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get current monitor mode: %s", error); - return false; - } + palGetCurrentMonitorMode(monitor, ¤t); palLog(nullptr, ""); palLog(nullptr, " Current Mode:"); @@ -130,5 +103,5 @@ bool monitorModeTest() // free monitors array palFree(nullptr, monitors); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/monitor_test.c b/tests/video/monitor_test.c similarity index 65% rename from tests/monitor_test.c rename to tests/video/monitor_test.c index 6d65e608..b2cf099f 100644 --- a/tests/monitor_test.c +++ b/tests/video/monitor_test.c @@ -1,38 +1,28 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool monitorTest() +PalBool monitorTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Monitor Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalMonitorInfo info; - Int32 count = 0; - // initialize the video system - PalResult result = palInitVideo(nullptr, nullptr); + PalResult result = palInitVideo(nullptr, nullptr, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // get the number of connected monitors + uint32_t count = 0; result = palEnumerateMonitors(&count, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors: %s", error); - return false; + logResult(result, "Failed to get query monitors"); + return PAL_FALSE; } // if count == 0, we fail if (count == 0) { palLog(nullptr, "No monitor connected"); - return false; + return PAL_FALSE; } palLog(nullptr, "Monitor Count: %d", count); @@ -42,27 +32,21 @@ bool monitorTest() monitors = palAllocate(nullptr, sizeof(PalMonitor*) * count, 0); if (!monitors) { palLog(nullptr, "Failed to allocate memory"); - return false; + return PAL_FALSE; } // get the handle of the connected monitors result = palEnumerateMonitors(&count, monitors); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get query monitors: %s", error); - return false; + logResult(result, "Failed to get query monitors"); + return PAL_FALSE; } // get monitor info for every monitor and log the information - for (Int32 i = 0; i < count; i++) { + PalMonitorInfo info = {0}; + for (int32_t i = 0; i < count; i++) { PalMonitor* monitor = monitors[i]; - result = palGetMonitorInfo(monitor, &info); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to get monitor info: %s", error); - palFree(nullptr, monitors); - return false; - } + palGetMonitorInfo(monitor, &info); // log monitor info palLog(nullptr, "Monitor Name: %s", info.name); @@ -107,5 +91,5 @@ bool monitorTest() // free monitors array palFree(nullptr, monitors); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/native_instance_test.c b/tests/video/native_instance_test.c similarity index 78% rename from tests/native_instance_test.c rename to tests/video/native_instance_test.c index dd4f2bed..f5f1f684 100644 --- a/tests/native_instance_test.c +++ b/tests/video/native_instance_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" #ifdef _WIN32 @@ -115,14 +115,10 @@ static inline int wlRegistryAddListener( const struct wl_registry_listener* listener, void* data) { - return s_wl_proxy_add_listener( - (struct wl_proxy*)wl_registry, - (void (**)(void))listener, - data); + return s_wl_proxy_add_listener((struct wl_proxy*)wl_registry, (void (**)(void))listener, data); } -static inline struct wl_registry* -wlDisplayGetRegistry(struct wl_display* wl_display) +static inline struct wl_registry* wlDisplayGetRegistry(struct wl_display* wl_display) { struct wl_proxy* registry; registry = s_wl_proxy_marshal_flags( @@ -136,7 +132,7 @@ wlDisplayGetRegistry(struct wl_display* wl_display) return (struct wl_registry*)registry; } -static bool s_Logged = false; +static PalBool s_Logged = PAL_FALSE; static void globalHandle( void* data, struct wl_registry* registry, @@ -146,7 +142,7 @@ static void globalHandle( { if (!s_Logged) { palLog(nullptr, "Registry global handle working"); - s_Logged = true; + s_Logged = PAL_TRUE; } } @@ -157,7 +153,7 @@ static void globalRemove( { if (s_Logged) { palLog(nullptr, "Registry global remove working"); - s_Logged = false; + s_Logged = PAL_FALSE; } } @@ -165,7 +161,7 @@ static const struct wl_registry_listener s_RegistryListener = { .global = globalHandle, .global_remove = globalRemove}; -static bool s_OnWayland = false; +static PalBool s_OnWayland = PAL_FALSE; #endif // _WIN32 @@ -180,15 +176,17 @@ void* openDisplayX11() // clang-format off s_XOpenDisplay = (XOpenDisplayFn)dlsym( - s_LibX, + s_LibX, "XOpenDisplay"); s_XCloseDisplay = (XCloseDisplayFn)dlsym( - s_LibX, + s_LibX, "XCloseDisplay"); + // clang-format on return s_XOpenDisplay(nullptr); #endif // __linux__ + return nullptr; } void closeDisplayX11(void* instance) @@ -210,35 +208,35 @@ void* openDisplayWayland() // clang-format off s_wl_display_connect = (wl_display_connect_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_connect"); s_wl_display_disconnect = (wl_display_disconnect_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_disconnect"); s_wl_display_roundtrip = (wl_display_roundtrip_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_display_roundtrip"); s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_marshal_flags"); s_wl_proxy_get_version = (wl_proxy_get_version_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_get_version"); s_wl_proxy_add_listener = (wl_proxy_add_listener_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_add_listener"); s_wl_proxy_destroy = (wl_proxy_destroy_fn)dlsym( - s_LibWayland, + s_LibWayland, "wl_proxy_destroy"); + // clang-format on registryInterface = dlsym(s_LibWayland, "wl_registry_interface"); - struct wl_display* display = s_wl_display_connect(nullptr); if (display) { s_Registry = wlDisplayGetRegistry(display); @@ -248,6 +246,7 @@ void* openDisplayWayland() return display; #endif // __linux__ + return nullptr; } void closeDisplayWayland(void* instance) @@ -261,9 +260,10 @@ void closeDisplayWayland(void* instance) void* openDisplayWin32() { -#ifdef __WIN32 +#ifdef _WIN32 return GetModuleHandleW(nullptr); -#endif // __WIN32 +#endif // _WIN32 + return nullptr; } void closeDisplayWin32(void* instance) @@ -280,9 +280,9 @@ void* openInstance() const char* session = getenv("XDG_SESSION_TYPE"); if (session) { if (strcmp(session, "wayland") == 0) { - s_OnWayland = true; + s_OnWayland = PAL_TRUE; } else { - s_OnWayland = false; + s_OnWayland = PAL_FALSE; } } @@ -307,49 +307,40 @@ void closeInstance(void* instance) #endif // _WIN32 } -bool nativeInstanceTest() +PalBool nativeInstanceTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Native Instance Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - // event driver - PalEventDriver* eventDriver = nullptr; + // fill the event driver create info PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = nullptr; // no callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // open our own display or instance void* instance = openInstance(); if (!instance) { palLog(nullptr, "Failed to open instance"); - return false; + return PAL_FALSE; } - // tell the video system to use out instance rather - // than creating a new one - // this can be set to the opengl system as well - palSetPreferredInstance(instance); - // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + // tell the video system to use out instance rather than creating a new one + result = palInitVideo(nullptr, eventDriver, instance); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } PalWindow* window = nullptr; @@ -357,13 +348,13 @@ bool nativeInstanceTest() createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Native Instance Test"; - + // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; @@ -371,23 +362,15 @@ bool nativeInstanceTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_KEYDOWN, - PAL_DISPATCH_POLL); - - bool running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -395,16 +378,16 @@ bool nativeInstanceTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -424,5 +407,5 @@ bool nativeInstanceTest() // the video system does not destroy or close the handle provided closeInstance(instance); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/native_integration_test.c b/tests/video/native_integration_test.c similarity index 72% rename from tests/native_integration_test.c rename to tests/video/native_integration_test.c index 80eabd79..c28d0a1c 100644 --- a/tests/native_integration_test.c +++ b/tests/video/native_integration_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" #ifdef _WIN32 @@ -112,7 +112,7 @@ static XFreeFn s_XFree; static Atom s_NET_WM_NAME; static Atom s_UTF8_STRING; -static bool s_OnWayland = false; +static PalBool s_OnWayland = PAL_FALSE; static void* s_X11Lib; static void* s_WaylandLib; @@ -121,7 +121,7 @@ static void* s_WaylandLib; static char s_TitleBuffer[32]; -void setWindowTitleX11(PalWindowHandleInfoEx* windowInfo) +void setWindowTitleX11(PalWindowHandleInfo* windowInfo) { #ifdef __linux__ // load the procs @@ -132,37 +132,36 @@ void setWindowTitleX11(PalWindowHandleInfoEx* windowInfo) // clang-format off s_XInternAtom = (XInternAtomFn)dlsym( - s_X11Lib, + s_X11Lib, "XInternAtom"); s_XChangeProperty = (XChangePropertyFn)dlsym( - s_X11Lib, + s_X11Lib, "XChangeProperty"); s_XGetWMName = (XGetWMNameFn)dlsym( - s_X11Lib, + s_X11Lib, "XGetWMName"); s_XStoreName = (XStoreNameFn)dlsym( - s_X11Lib, + s_X11Lib, "XStoreName"); s_XGetWindowProperty = (XGetWindowPropertyFn)dlsym( - s_X11Lib, + s_X11Lib, "XGetWindowProperty"); s_XFlush = (XFlushFn)dlsym( - s_X11Lib, + s_X11Lib, "XFlush"); s_XFree = (XFreeFn)dlsym( - s_X11Lib, + s_X11Lib, "XFree"); - // clang-format on - Display* display = (Display*)windowInfo->nativeDisplay; - Window window = (Window)(UintPtr)windowInfo->nativeWindow; + Display* display = (Display*)windowInfo->nativeInstance; + Window window = (Window)(uintptr_t)windowInfo->nativeWindow; s_NET_WM_NAME = s_XInternAtom(display, "_NET_WM_NAME", False); s_UTF8_STRING = s_XInternAtom(display, "UTF8_STRING", False); @@ -176,7 +175,7 @@ void setWindowTitleX11(PalWindowHandleInfoEx* windowInfo) s_UTF8_STRING, 8, // unsigned char PropModeReplace, - title, + (const unsigned char*)title, strlen(title)); } else { @@ -187,11 +186,11 @@ void setWindowTitleX11(PalWindowHandleInfoEx* windowInfo) #endif // __linux__ } -void getWindowTitleX11(PalWindowHandleInfoEx* windowInfo) +void getWindowTitleX11(PalWindowHandleInfo* windowInfo) { #ifdef __linux__ - Display* display = (Display*)windowInfo->nativeDisplay; - Window window = (Window)(UintPtr)windowInfo->nativeWindow; + Display* display = (Display*)windowInfo->nativeInstance; + Window window = (Window)(uintptr_t)windowInfo->nativeWindow; if (s_NET_WM_NAME) { Atom type; @@ -228,7 +227,7 @@ void getWindowTitleX11(PalWindowHandleInfoEx* windowInfo) #endif // __linux__ } -void setWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) +void setWindowTitleWayland(PalWindowHandleInfo* windowInfo) { #ifdef __linux__ s_WaylandLib = dlopen("libwayland-client.so.0", RTLD_LAZY); @@ -236,19 +235,15 @@ void setWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) return; } - s_wl_proxy_marshal_flags = (wl_proxy_marshal_flags_fn)dlsym( - s_WaylandLib, - "wl_proxy_marshal_flags"); - - s_wl_proxy_get_version = - (wl_proxy_get_version_fn)dlsym(s_WaylandLib, "wl_proxy_get_version"); + s_wl_proxy_marshal_flags = + (wl_proxy_marshal_flags_fn)dlsym(s_WaylandLib, "wl_proxy_marshal_flags"); - s_wl_display_flush = - (wl_display_flush_fn)dlsym(s_WaylandLib, "wl_display_flush"); + s_wl_proxy_get_version = (wl_proxy_get_version_fn)dlsym(s_WaylandLib, "wl_proxy_get_version"); + s_wl_display_flush = (wl_display_flush_fn)dlsym(s_WaylandLib, "wl_display_flush"); struct xdg_toplevel* toplevel = nullptr; struct wl_display* display = nullptr; - display = (struct wl_display*)windowInfo->nativeDisplay; + display = (struct wl_display*)windowInfo->nativeInstance; toplevel = (struct xdg_toplevel*)windowInfo->nativeHandle2; xdgToplevelSetTitle(toplevel, "Hello from native Wayland API"); @@ -257,16 +252,15 @@ void setWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) #endif // __linux__ } -void getWindowTitleWayland(PalWindowHandleInfoEx* windowInfo) +void getWindowTitleWayland(PalWindowHandleInfo* windowInfo) { #ifdef __linux__ // wayland does not support getting window title // so we just return the title we set through wayland - dlclose(s_WaylandLib); #endif // __linux__ } -void setWindowTitleWin32(PalWindowHandleInfoEx* windowInfo) +void setWindowTitleWin32(PalWindowHandleInfo* windowInfo) { #ifdef _WIN32 const char* title = "Hello from native Win32 API"; @@ -274,17 +268,14 @@ void setWindowTitleWin32(PalWindowHandleInfoEx* windowInfo) #endif // _WIN32 } -void getWindowTitleWin32(PalWindowHandleInfoEx* windowInfo) +void getWindowTitleWin32(PalWindowHandleInfo* windowInfo) { #ifdef _WIN32 - GetWindowTextA( - (HWND)windowInfo->nativeWindow, - s_TitleBuffer, - sizeof(s_TitleBuffer)); + GetWindowTextA((HWND)windowInfo->nativeWindow, s_TitleBuffer, sizeof(s_TitleBuffer)); #endif // _WIN32 } -void setWindowTitle(PalWindowHandleInfoEx* windowInfo) +void setWindowTitle(PalWindowHandleInfo* windowInfo) { #ifdef _WIN32 setWindowTitleWin32(windowInfo); @@ -293,9 +284,9 @@ void setWindowTitle(PalWindowHandleInfoEx* windowInfo) const char* session = getenv("XDG_SESSION_TYPE"); if (session) { if (strcmp(session, "wayland") == 0) { - s_OnWayland = true; + s_OnWayland = PAL_TRUE; } else { - s_OnWayland = false; + s_OnWayland = PAL_FALSE; } } @@ -307,7 +298,7 @@ void setWindowTitle(PalWindowHandleInfoEx* windowInfo) #endif // _WIN32 } -void getWindowTitle(PalWindowHandleInfoEx* windowInfo) +void getWindowTitle(PalWindowHandleInfo* windowInfo) { #ifdef _WIN32 getWindowTitleWin32(windowInfo); @@ -320,37 +311,32 @@ void getWindowTitle(PalWindowHandleInfoEx* windowInfo) #endif // _WIN32 } -bool nativeIntegrationTest() +PalBool nativeIntegrationTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Native Integration Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - // event driver - PalEventDriver* eventDriver = nullptr; + // fill the event driver create info PalEventDriverCreateInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.allocator = nullptr; // default allocator + eventDriverCreateInfo.callback = nullptr; // no callback dispatch + eventDriverCreateInfo.queue = nullptr; // default queue + eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } PalWindow* window = nullptr; @@ -358,13 +344,13 @@ bool nativeIntegrationTest() createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "Native Integration Test"; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; @@ -372,34 +358,35 @@ bool nativeIntegrationTest() result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // set the window title using native APIs - PalWindowHandleInfoEx windowInfo = {0}; - windowInfo = palGetWindowHandleInfoEx(window); + PalWindowHandleInfo windowInfo = {0}; + palGetWindowHandleInfo(window, &windowInfo); palLog(nullptr, "Window title: %s", createInfo.title); palLog(nullptr, "Setting window title with native API"); setWindowTitle(&windowInfo); palLog(nullptr, "Getting window title with PAL API"); - palGetWindowTitle(window, sizeof(s_TitleBuffer), nullptr, s_TitleBuffer); + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_TITLE) { + palGetWindowTitle(window, sizeof(s_TitleBuffer), nullptr, s_TitleBuffer); + } else { + memset(s_TitleBuffer, 0, sizeof(s_TitleBuffer)); + } palLog(nullptr, "Window title: %s", s_TitleBuffer); // set the title with PAL and retreive it with the native API palLog(nullptr, "Setting window title with PAL API"); - palSetWindowTitle(window, "Hello from PAL API"); + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_TITLE) { + palSetWindowTitle(window, "Hello from PAL API"); + } palLog(nullptr, "Getting window title with native API"); getWindowTitle(&windowInfo); @@ -408,7 +395,7 @@ bool nativeIntegrationTest() // using native API like wl_proxy_set_user_data, XContext and // SetWindowLongPtr(GWLP_USERDATA) can be used freely - bool running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -416,16 +403,16 @@ bool nativeIntegrationTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -442,5 +429,5 @@ bool nativeIntegrationTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/system_cursor_test.c b/tests/video/system_cursor_test.c similarity index 59% rename from tests/system_cursor_test.c rename to tests/video/system_cursor_test.c index a0f4ec73..ed63cd72 100644 --- a/tests/system_cursor_test.c +++ b/tests/video/system_cursor_test.c @@ -1,102 +1,84 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" -bool systemCursorTest() +PalBool systemCursorTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "System Cursor Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalWindow* window = nullptr; - PalCursor* cursor = nullptr; - PalWindowCreateInfo createInfo = {0}; - bool running = false; - - // event driver - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator - eventDriverCreateInfo.callback = nullptr; // for callback dispatch + eventDriverCreateInfo.callback = nullptr; // no callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } // initialize the video system. We pass the event driver to recieve video // related events the video does not copy this, this must be valid till the // video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // check for support - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR)) { palLog(nullptr, "Seting cursors feature not supported"); palDestroyEventDriver(eventDriver); palShutdownVideo(); - return false; + return PAL_FALSE; } // create system cursor - result = palCreateCursorFrom(PAL_CURSOR_CROSS, &cursor); + PalCursor* cursor = nullptr; + result = palCreateCursorFrom(PAL_CURSOR_TYPE_CROSS, &cursor); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window cursor: %s", error); - return false; + logResult(result, "Failed to create window cursor"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; createInfo.title = "System Cursor Window - Cross"; // check if we support decorated windows (title bar, close etc) - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; } // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } // set the dispatch mode for window close event to recieve it - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); // polling - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // set the cursor palSetWindowCursor(window, cursor); - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -104,16 +86,16 @@ bool systemCursorTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } @@ -133,5 +115,5 @@ bool systemCursorTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/video/video_test.c b/tests/video/video_test.c new file mode 100644 index 00000000..6bc23f84 --- /dev/null +++ b/tests/video/video_test.c @@ -0,0 +1,181 @@ + +#include "pal2/pal_video.h" +#include "tests.h" + +PalBool videoTest() +{ + // initialize the video system + PalResult result = palInitVideo(nullptr, nullptr, nullptr); + if (result != PAL_RESULT_SUCCESS) { + logResult(result, "Failed to initialize video"); + return PAL_FALSE; + } + + // get supported features + PalVideoFeatures features = palGetVideoFeatures(); + palLog(nullptr, "Supported Video Features:"); + if (features & PAL_VIDEO_FEATURE_HIGH_DPI) { + palLog(nullptr, " High DPI windows"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_SET_ORIENTATION) { + palLog(nullptr, " Setting monitor orientation"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_GET_ORIENTATION) { + palLog(nullptr, " Getting monitor orientation"); + } + + if (features & PAL_VIDEO_FEATURE_BORDERLESS_WINDOW) { + palLog(nullptr, " Borderless windows"); + } + + if (features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW) { + palLog(nullptr, " Transparent windows"); + } + + if (features & PAL_VIDEO_FEATURE_TOOL_WINDOW) { + palLog(nullptr, " Tool windows"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_SET_MODE) { + palLog(nullptr, " Setting monitor display mode"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_GET_MODE) { + palLog(nullptr, " Getting monitor display mode"); + } + + if (features & PAL_VIDEO_FEATURE_MULTI_MONITORS) { + palLog(nullptr, " Multi monitors"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_SIZE) { + palLog(nullptr, " Setting window size"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_SIZE) { + palLog(nullptr, " Getting window size"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_POS) { + palLog(nullptr, " Setting window position"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_POS) { + palLog(nullptr, " Getting window position"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STATE) { + palLog(nullptr, " Setting window state"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STATE) { + palLog(nullptr, " Getting window state"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_VISIBILITY) { + palLog(nullptr, " Setting window visibility"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_VISIBILITY) { + palLog(nullptr, " Getting window visibility"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_TITLE) { + palLog(nullptr, " Setting window title"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_TITLE) { + palLog(nullptr, " Getting window title"); + } + + if (features & PAL_VIDEO_FEATURE_NO_MINIMIZEBOX) { + palLog(nullptr, " No minimize box for windows"); + } + + if (features & PAL_VIDEO_FEATURE_NO_MAXIMIZEBOX) { + palLog(nullptr, " No maximize box for windows"); + } + + if (features & PAL_VIDEO_FEATURE_CLIP_CURSOR) { + palLog(nullptr, " Clipping cursor (mouse)"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_CAPTION) { + palLog(nullptr, " Window titlebar flashing"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_TRAY) { + palLog(nullptr, " Window icon on taskbar flashing"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_FLASH_INTERVAL) { + palLog(nullptr, " Setting window flash interval"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_INPUT_FOCUS) { + palLog(nullptr, " Setting input window"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_INPUT_FOCUS) { + palLog(nullptr, " Getting input window"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_STYLE) { + palLog(nullptr, " Setting window style"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_STYLE) { + palLog(nullptr, " Getting window style"); + } + + if (features & PAL_VIDEO_FEATURE_CURSOR_SET_POS) { + palLog(nullptr, " Setting cursor position"); + } + + if (features & PAL_VIDEO_FEATURE_CURSOR_GET_POS) { + palLog(nullptr, " Getting cursor position"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_ICON) { + palLog(nullptr, " Setting window icon"); + } + + if (features & PAL_VIDEO_FEATURE_TOPMOST_WINDOW) { + palLog(nullptr, " Topmost windows"); + } + + if (features & PAL_VIDEO_FEATURE_DECORATED_WINDOW) { + palLog(nullptr, " Decorated (Normal) windows"); + } + + if (features & PAL_VIDEO_FEATURE_CURSOR_SET_VISIBILITY) { + palLog(nullptr, " Setting cursor visibility"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_GET_MONITOR) { + palLog(nullptr, " Getting window current monitor"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_GET_PRIMARY) { + palLog(nullptr, " Getting primary monitor"); + } + + if (features & PAL_VIDEO_FEATURE_FOREIGN_WINDOWS) { + palLog(nullptr, " Attaching and detaching foreign windows"); + } + + if (features & PAL_VIDEO_FEATURE_MONITOR_VALIDATE_MODE) { + palLog(nullptr, " Validate monitor display mode"); + } + + if (features & PAL_VIDEO_FEATURE_WINDOW_SET_CURSOR) { + palLog(nullptr, " Setting window cursor"); + } + + // shutdown the video system + palShutdownVideo(); + + return PAL_TRUE; +} diff --git a/tests/window_test.c b/tests/video/window_test.c similarity index 71% rename from tests/window_test.c rename to tests/video/window_test.c index 7210dabc..1707fa72 100644 --- a/tests/window_test.c +++ b/tests/video/window_test.c @@ -1,5 +1,5 @@ -#include "pal/pal_video.h" +#include "pal2/pal_video.h" #include "tests.h" // make the window borderless if supported @@ -30,20 +30,15 @@ static const char* dispatchString = "Callback Mode"; // inline helpers static inline void onWindowResize(const PalEvent* event) { - Uint32 width, height; // width == low, height == high + uint32_t width, height; // width == low, height == high palUnpackUint32(event->data, &width, &height); PalWindow* window = palUnpackPointer(event->data2); - palLog( - nullptr, - "%s: Window Resized: (%d, %d)", - dispatchString, - width, - height); + palLog(nullptr, "%s: Window Resized: (%d, %d)", dispatchString, width, height); } static inline void onWindowMove(const PalEvent* event) { - Int32 x, y; // x == low, y == high + int32_t x, y; // x == low, y == high palUnpackInt32(event->data, &x, &y); PalWindow* window = palUnpackPointer(event->data2); palLog(nullptr, "%s: Window Moved: (%d, %d)", dispatchString, x, y); @@ -115,31 +110,31 @@ static void PAL_CALL onEvent( void* userData, const PalEvent* event) { - if (event->type == PAL_EVENT_WINDOW_SIZE) { + if (event->type == PAL_EVENT_TYPE_WINDOW_SIZE) { onWindowResize(event); - } else if (event->type == PAL_EVENT_WINDOW_MOVE) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_MOVE) { onWindowMove(event); - } else if (event->type == PAL_EVENT_WINDOW_VISIBILITY) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_VISIBILITY) { onWindowVisibility(event); - } else if (event->type == PAL_EVENT_WINDOW_FOCUS) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_FOCUS) { onWindowFocus(event); - } else if (event->type == PAL_EVENT_WINDOW_STATE) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_STATE) { onWindowState(event); - } else if (event->type == PAL_EVENT_WINDOW_MODAL_BEGIN) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_MODAL_BEGIN) { onWindowModalBegin(event); - } else if (event->type == PAL_EVENT_WINDOW_MODAL_END) { + } else if (event->type == PAL_EVENT_TYPE_WINDOW_MODAL_END) { onWindowModalEnd(event); - } else if (event->type == PAL_EVENT_MONITOR_DPI_CHANGED) { + } else if (event->type == PAL_EVENT_TYPE_MONITOR_DPI_CHANGED) { onMonitorDPI(event); - } else if (event->type == PAL_EVENT_MONITOR_LIST_CHANGED) { + } else if (event->type == PAL_EVENT_TYPE_MONITOR_LIST_CHANGED) { onMonitorList(event); } else { @@ -147,95 +142,76 @@ static void PAL_CALL onEvent( } } -bool windowTest() +PalBool windowTest() { - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Window Test"); palLog(nullptr, "Press Escape or click close button to close Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - PalWindow* window = nullptr; - PalWindowCreateInfo createInfo = {0}; - bool running = false; - - // event driver - PalEventDriver* eventDriver = nullptr; - PalEventDriverCreateInfo eventDriverCreateInfo = {0}; // fill the event driver create info + PalEventDriverCreateInfo eventDriverCreateInfo = {0}; eventDriverCreateInfo.allocator = nullptr; // default allocator eventDriverCreateInfo.callback = onEvent; // for callback dispatch eventDriverCreateInfo.queue = nullptr; // default queue eventDriverCreateInfo.userData = nullptr; // null // create the event driver - result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); + PalEventDriver* eventDriver = nullptr; + PalResult result = palCreateEventDriver(&eventDriverCreateInfo, &eventDriver); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create event driver: %s", error); - return false; + logResult(result, "Failed to create event driver"); + return PAL_FALSE; } - PalDispatchMode dispatchMode = PAL_DISPATCH_NONE; + PalDispatchMode dispatchMode = PAL_DISPATCH_MODE_NONE; #if DISPATCH_MODE_POLL - dispatchMode = PAL_DISPATCH_POLL; + dispatchMode = PAL_DISPATCH_MODE_POLL; #else - dispatchMode = PAL_DISPATCH_CALLBACK; + dispatchMode = PAL_DISPATCH_MODE_CALLBACK; #endif // DISPATCH_MODE_POLL // set dispatch mode for all events. - for (Uint32 e = 0; e < PAL_EVENT_KEYDOWN; e++) { + for (uint32_t e = 0; e < PAL_EVENT_TYPE_KEYDOWN; e++) { palSetEventDispatchMode(eventDriver, e, dispatchMode); } // we set window close to poll - palSetEventDispatchMode( - eventDriver, - PAL_EVENT_WINDOW_CLOSE, - PAL_DISPATCH_POLL); - - palSetEventDispatchMode(eventDriver, PAL_EVENT_KEYDOWN, PAL_DISPATCH_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_WINDOW_CLOSE, PAL_DISPATCH_MODE_POLL); + palSetEventDispatchMode(eventDriver, PAL_EVENT_TYPE_KEYDOWN, PAL_DISPATCH_MODE_POLL); // we set callback mode for modal begin and end. Since we want to capture // that instantly palSetEventDispatchMode( eventDriver, - PAL_EVENT_WINDOW_MODAL_BEGIN, - PAL_DISPATCH_CALLBACK); - + PAL_EVENT_TYPE_WINDOW_MODAL_BEGIN, + PAL_DISPATCH_MODE_CALLBACK); palSetEventDispatchMode( eventDriver, - PAL_EVENT_WINDOW_MODAL_END, - PAL_DISPATCH_CALLBACK); - + PAL_EVENT_TYPE_WINDOW_MODAL_END, + PAL_DISPATCH_MODE_CALLBACK); palSetEventDispatchMode( eventDriver, - PAL_EVENT_WINDOW_DECORATION_MODE, - PAL_DISPATCH_POLL); + PAL_EVENT_TYPE_WINDOW_DECORATION_MODE, + PAL_DISPATCH_MODE_POLL); // initialize the video system. We pass the event driver to recieve video // related events the video system does not copy the event driver, it must // be valid till the video system is shutdown - result = palInitVideo(nullptr, eventDriver); + result = palInitVideo(nullptr, eventDriver, nullptr); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; + logResult(result, "Failed to initialize video"); + return PAL_FALSE; } // fill the create info struct + PalWindowCreateInfo createInfo = {0}; createInfo.monitor = nullptr; // use default monitor createInfo.height = 480; createInfo.width = 640; - createInfo.show = true; + createInfo.show = PAL_TRUE; createInfo.style = PAL_WINDOW_STYLE_RESIZABLE; // check if we support decorated windows (title bar, close etc) - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - if (!(features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW)) { + PalVideoFeatures features = palGetVideoFeatures(); + if (!(features & PAL_VIDEO_FEATURE_DECORATED_WINDOW)) { // if we dont support, we need to create a borderless window // and create the decorations ourselves createInfo.style |= PAL_WINDOW_STYLE_BORDERLESS; @@ -278,25 +254,20 @@ bool windowTest() #endif // NO_MAXIMIZEBOX // create the window with the create info struct + PalWindow* window = nullptr; result = palCreateWindow(&createInfo, &window); if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to create window: %s", error); - return false; + logResult(result, "Failed to create window"); + return PAL_FALSE; } #if MAKE_TRANSPARENT if (features & PAL_VIDEO_FEATURE_TRANSPARENT_WINDOW) { - result = palSetWindowOpacity(window, OPACITY); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to set window opacity: %s", error); - return false; - } + palSetWindowOpacity(window, OPACITY); } #endif // MAKE_TRANSPARENT - running = true; + PalBool running = PAL_TRUE; while (running) { // update the video system to push video events palUpdateVideo(); @@ -304,56 +275,56 @@ bool windowTest() PalEvent event; while (palPollEvent(eventDriver, &event)) { switch (event.type) { - case PAL_EVENT_WINDOW_CLOSE: { - running = false; + case PAL_EVENT_TYPE_WINDOW_CLOSE: { + running = PAL_FALSE; break; } - case PAL_EVENT_WINDOW_SIZE: { + case PAL_EVENT_TYPE_WINDOW_SIZE: { onWindowResize(&event); break; } - case PAL_EVENT_WINDOW_MOVE: { + case PAL_EVENT_TYPE_WINDOW_MOVE: { onWindowMove(&event); break; } - case PAL_EVENT_WINDOW_VISIBILITY: { + case PAL_EVENT_TYPE_WINDOW_VISIBILITY: { onWindowVisibility(&event); break; } - case PAL_EVENT_WINDOW_STATE: { + case PAL_EVENT_TYPE_WINDOW_STATE: { onWindowState(&event); break; } - case PAL_EVENT_WINDOW_FOCUS: { + case PAL_EVENT_TYPE_WINDOW_FOCUS: { onWindowFocus(&event); break; } - case PAL_EVENT_MONITOR_DPI_CHANGED: { + case PAL_EVENT_TYPE_MONITOR_DPI_CHANGED: { onMonitorDPI(&event); break; } - case PAL_EVENT_MONITOR_LIST_CHANGED: { + case PAL_EVENT_TYPE_MONITOR_LIST_CHANGED: { onMonitorList(&event); break; } - case PAL_EVENT_KEYDOWN: { + case PAL_EVENT_TYPE_KEYDOWN: { PalKeycode keycode = 0; palUnpackUint32(event.data, &keycode, nullptr); if (keycode == PAL_KEYCODE_ESCAPE) { - running = false; + running = PAL_FALSE; } break; } - case PAL_EVENT_WINDOW_DECORATION_MODE: { + case PAL_EVENT_TYPE_WINDOW_DECORATION_MODE: { if (event.data == PAL_DECORATION_MODE_CLIENT_SIDE) { palLog(nullptr, "Window Decoration Mode: Client Side"); } else { @@ -376,5 +347,5 @@ bool windowTest() // destroy the event driver palDestroyEventDriver(eventDriver); - return true; -} \ No newline at end of file + return PAL_TRUE; +} diff --git a/tests/video_test.c b/tests/video_test.c deleted file mode 100644 index c018c741..00000000 --- a/tests/video_test.c +++ /dev/null @@ -1,190 +0,0 @@ - -#include "pal/pal_video.h" -#include "tests.h" - -bool videoTest() -{ - palLog(nullptr, ""); - palLog(nullptr, "==========================================="); - palLog(nullptr, "Video Test"); - palLog(nullptr, "==========================================="); - palLog(nullptr, ""); - - PalResult result; - - // initialize the video system - result = palInitVideo(nullptr, nullptr); - if (result != PAL_RESULT_SUCCESS) { - const char* error = palFormatResult(result); - palLog(nullptr, "Failed to initialize video: %s", error); - return false; - } - - // get supported features. Now uses extended function - PalVideoFeatures64 features = palGetVideoFeaturesEx(); - palLog(nullptr, "Supported Video Features:"); - if (features & PAL_VIDEO_FEATURE64_HIGH_DPI) { - palLog(nullptr, " High DPI windows"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_SET_ORIENTATION) { - palLog(nullptr, " Setting monitor orientation"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_ORIENTATION) { - palLog(nullptr, " Getting monitor orientation"); - } - - if (features & PAL_VIDEO_FEATURE64_BORDERLESS_WINDOW) { - palLog(nullptr, " Borderless windows"); - } - - if (features & PAL_VIDEO_FEATURE64_TRANSPARENT_WINDOW) { - palLog(nullptr, " Transparent windows"); - } - - if (features & PAL_VIDEO_FEATURE64_TOOL_WINDOW) { - palLog(nullptr, " Tool windows"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_SET_MODE) { - palLog(nullptr, " Setting monitor display mode"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_MODE) { - palLog(nullptr, " Getting monitor display mode"); - } - - if (features & PAL_VIDEO_FEATURE64_MULTI_MONITORS) { - palLog(nullptr, " Multi monitors"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_SIZE) { - palLog(nullptr, " Setting window size"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_SIZE) { - palLog(nullptr, " Getting window size"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_POS) { - palLog(nullptr, " Setting window position"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_POS) { - palLog(nullptr, " Getting window position"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_STATE) { - palLog(nullptr, " Setting window state"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_STATE) { - palLog(nullptr, " Getting window state"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_VISIBILITY) { - palLog(nullptr, " Setting window visibility"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_VISIBILITY) { - palLog(nullptr, " Getting window visibility"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_TITLE) { - palLog(nullptr, " Setting window title"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_TITLE) { - palLog(nullptr, " Getting window title"); - } - - if (features & PAL_VIDEO_FEATURE64_NO_MINIMIZEBOX) { - palLog(nullptr, " No minimize box for windows"); - } - - if (features & PAL_VIDEO_FEATURE64_NO_MAXIMIZEBOX) { - palLog(nullptr, " No maximize box for windows"); - } - - if (features & PAL_VIDEO_FEATURE64_CLIP_CURSOR) { - palLog(nullptr, " Clipping cursor (mouse)"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_CAPTION) { - palLog(nullptr, " Window titlebar flashing"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_TRAY) { - palLog(nullptr, " Window icon on taskbar flashing"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_FLASH_INTERVAL) { - palLog(nullptr, " Setting window flash interval"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_INPUT_FOCUS) { - palLog(nullptr, " Setting input window"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_INPUT_FOCUS) { - palLog(nullptr, " Getting input window"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_STYLE) { - palLog(nullptr, " Setting window style"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_STYLE) { - palLog(nullptr, " Getting window style"); - } - - if (features & PAL_VIDEO_FEATURE64_CURSOR_SET_POS) { - palLog(nullptr, " Setting cursor position"); - } - - if (features & PAL_VIDEO_FEATURE64_CURSOR_GET_POS) { - palLog(nullptr, " Getting cursor position"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_ICON) { - palLog(nullptr, " Setting window icon"); - } - - if (features & PAL_VIDEO_FEATURE64_TOPMOST_WINDOW) { - palLog(nullptr, " Topmost windows"); - } - - if (features & PAL_VIDEO_FEATURE64_DECORATED_WINDOW) { - palLog(nullptr, " Decorated (Normal) windows"); - } - - if (features & PAL_VIDEO_FEATURE64_CURSOR_SET_VISIBILITY) { - palLog(nullptr, " Setting cursor visibility"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_GET_MONITOR) { - palLog(nullptr, " Getting window current monitor"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_GET_PRIMARY) { - palLog(nullptr, " Getting primary monitor"); - } - - if (features & PAL_VIDEO_FEATURE64_FOREIGN_WINDOWS) { - palLog(nullptr, " Attaching and detaching foreign windows"); - } - - if (features & PAL_VIDEO_FEATURE64_MONITOR_VALIDATE_MODE) { - palLog(nullptr, " Validate monitor display mode"); - } - - if (features & PAL_VIDEO_FEATURE64_WINDOW_SET_CURSOR) { - palLog(nullptr, " Setting window cursor"); - } - - // shutdown the video system - palShutdownVideo(); - - return true; -} \ No newline at end of file diff --git a/tools/abi_dump/abi_dump.lua b/tools/abi_dump/abi_dump.lua new file mode 100644 index 00000000..0dd558f2 --- /dev/null +++ b/tools/abi_dump/abi_dump.lua @@ -0,0 +1,24 @@ + +project "abi-dump" + kind "ConsoleApp" + + targetdir(targetDir) + objdir(objDir) + + files { + "core_abi_dump.c", + "event_abi_dump.c", + "thread_abi_dump.c", + "system_abi_dump.c", + "video_abi_dump.c", + "opengl_abi_dump.c", + "graphics_abi_dump.c", + "abi_dump_main.c" + } + + includedirs { + "%{wks.location}/include", + "%{wks.location}/tools/abi_dump" + } + + links { "PAL2" } diff --git a/tools/abi_dump/abi_dump_main.c b/tools/abi_dump/abi_dump_main.c new file mode 100644 index 00000000..8eaae783 --- /dev/null +++ b/tools/abi_dump/abi_dump_main.c @@ -0,0 +1,289 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" + +#define VERSION "1.0" +#define LOG_NAME "PAL ABI Dump" + +#ifdef _WIN32 +#define EXE_NAME "abi-dump.exe" +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif // WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX +#include + +#define DLL_HANDLE HMODULE +#define LOAD_DLL LoadLibraryA("PAL2.dll") +#define FREE_DLL(handle) FreeLibrary(handle) +#define GET_FUNC(handle, name) GetProcAddress(handle, name) + +#else +#define EXE_NAME "abi-dump" +#include + +#define DLL_HANDLE void* +#define LOAD_DLL dlopen("libPAL2.so", RTLD_LAZY) +#define FREE_DLL(handle) dlclose(handle) +#define GET_FUNC(handle, name) dlsym(handle, name) +#endif // _WIN32 + +typedef void(PAL_CALL* GetVersionFn)(PalVersion* version); + +static int logDumpStatus(PalBool status) +{ + int ret = -1; + const char* str = s_FailedString; + if (status) { + str = s_PassedString; + ret = 0; + } + + palLog(nullptr, "%s %s: %s", LOG_NAME, "Status", str); + return ret; +} + +static PalBool checkPALVersion(uint32_t flags) +{ + GetVersionFn getVersion = nullptr; + DLL_HANDLE handle = LOAD_DLL; + if (!handle) { + palLog(nullptr, "Failed to load PAL library"); + return PAL_FALSE; + } + + getVersion = (GetVersionFn)GET_FUNC(handle, "palGetVersion"); + if (!getVersion) { + palLog(nullptr, "Failed to load palGetVersion in PAL library"); + FREE_DLL(handle); + return PAL_FALSE; + } + + PalVersion version; + getVersion(&version); + FREE_DLL(handle); + + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "PAL ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + if (strcmp(VERSION, "1.0") == 0) { + // ABI Dump v1.0 == PAL v2.0.0 + if (version.major == 2 && version.minor == 0 && version.build == 0) { + if (flags & DUMP_FLAG_VERBOSE) { + palLog(nullptr, "Expected PAL Version: 2.0.0"); + palLog( + nullptr, + "Actual PAL Version: %d.%d.%d", + version.major, + version.minor, + version.build); + + palLog(nullptr, "Target Library: PAL2"); + } + return PAL_TRUE; + } + } + + return PAL_FALSE; +} + +// clang-format off +int main(int argc, char** argv) +{ + // clang-format on + PalBool status = PAL_FALSE; + PalBool dumpVersion = PAL_FALSE; + PalBool dumpHelp = PAL_FALSE; + uint32_t flags = 0; + uint32_t passed = 0; + uint32_t dumps = 0; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--core") == 0) { + dumps |= DUMP_FLAG_CORE; + + } else if (strcmp(argv[i], "--event") == 0) { + dumps |= DUMP_FLAG_EVENT; + + } else if (strcmp(argv[i], "--graphics") == 0) { + dumps |= DUMP_FLAG_GRAPHICS; + + } else if (strcmp(argv[i], "--opengl") == 0) { + dumps |= DUMP_FLAG_OPENGL; + + } else if (strcmp(argv[i], "--system") == 0) { + dumps |= DUMP_FLAG_SYSTEM; + + } else if (strcmp(argv[i], "--thread") == 0) { + dumps |= DUMP_FLAG_THREAD; + + } else if (strcmp(argv[i], "--video") == 0) { + dumps |= DUMP_FLAG_VIDEO; + + } else if (strcmp(argv[i], "--version") == 0) { + dumpVersion = PAL_TRUE; + + } else if (strcmp(argv[i], "--help") == 0) { + dumpHelp = PAL_TRUE; + + } else if (strcmp(argv[i], "--verbose") == 0) { + flags &= ~DUMP_FLAG_QUICK; + flags |= DUMP_FLAG_VERBOSE; + + } else if (strcmp(argv[i], "--quick") == 0) { + flags &= ~DUMP_FLAG_VERBOSE; + flags |= DUMP_FLAG_QUICK; + } + } + + if (dumps == 0 && dumpHelp == PAL_FALSE && dumpVersion == PAL_FALSE) { + dumps |= DUMP_FLAG_ALL; + } + + status = checkPALVersion(flags); + if (!status) { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + + if (dumps & DUMP_FLAG_CORE) { + status = coreABIDump(flags); + if (status) { + passed |= DUMP_FLAG_CORE; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_EVENT) { + status = eventABIDump(flags); + if (status) { + passed |= DUMP_FLAG_EVENT; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_THREAD) { + status = threadABIDump(flags); + if (status) { + passed |= DUMP_FLAG_THREAD; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_SYSTEM) { + status = systemABIDump(flags); + if (status) { + passed |= DUMP_FLAG_SYSTEM; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_VIDEO) { + status = videoABIDump(flags); + if (status) { + passed |= DUMP_FLAG_VIDEO; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_OPENGL) { + if (status) { + status = openglABIDump(flags); + passed |= DUMP_FLAG_OPENGL; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumps & DUMP_FLAG_GRAPHICS) { + status = graphicsABIDump(flags); + if (status) { + passed |= DUMP_FLAG_GRAPHICS; + } else { + if (flags & DUMP_FLAG_QUICK) { + return logDumpStatus(status); + } else { + return -1; + } + } + } + + if (dumpVersion) { + palLog(nullptr, "%s %s", LOG_NAME, VERSION); + } + + if (dumpHelp) { + palLog(nullptr, "USAGE: %s [options]", EXE_NAME); + palLog(nullptr, "Options:"); + palLog(nullptr, " --help Display available options"); + palLog(nullptr, " --version Display version"); + palLog(nullptr, " --verbose Display Detailed ABI dump information"); + palLog(nullptr, " --quick Display only the final ABI status"); + palLog(nullptr, " --core Check ABI for core PAL structs"); + palLog(nullptr, " --event Check ABI for event PAL structs"); + palLog(nullptr, " --graphics Check ABI for graphics PAL structs"); + palLog(nullptr, " --opengl Check ABI for opengl PAL structs"); + palLog(nullptr, " --system Check ABI for system PAL structs"); + palLog(nullptr, " --thread Check ABI for thread PAL structs"); + palLog(nullptr, " --video Check ABI for video PAL structs"); + } + + if (flags & DUMP_FLAG_QUICK) { + if (dumps == 0) { + return 0; + } + + // check if all dumps that were executed passed + if (dumps == passed) { + return logDumpStatus(PAL_TRUE); + } else { + return logDumpStatus(PAL_FALSE); + } + } + + return 0; +} diff --git a/tools/abi_dump/core_abi_dump.c b/tools/abi_dump/core_abi_dump.c new file mode 100644 index 00000000..5ccf101c --- /dev/null +++ b/tools/abi_dump/core_abi_dump.c @@ -0,0 +1,72 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" + +PalBool coreABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Core ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo versionFields[] = { + {"major", {0, 4}, FIELD(PalVersion, major)}, + {"minor", {4, 4}, FIELD(PalVersion, minor)}, + {"build", {8, 4}, FIELD(PalVersion, build)}}; + + FieldInfo allocatorFields[] = { + {"allocate", {0, 8}, FIELD(PalAllocator, allocate)}, + {"free", {8, 8}, FIELD(PalAllocator, free)}, + {"userData", {16, 8}, FIELD(PalAllocator, userData)}}; + + FieldInfo loggerFields[] = { + {"callback", {0, 8}, FIELD(PalLogger, callback)}, + {"userData", {8, 8}, FIELD(PalLogger, userData)}}; + + StructInfo versionInfo = {0}; + versionInfo.name = "PalVersion"; + versionInfo.fields = versionFields; + versionInfo.fieldCount = ARRAY_SIZE(versionFields); + versionInfo.expected.alignof = 4; + versionInfo.expected.size = 12; + versionInfo.expected.padding = 0; + versionInfo.actual = STRUCT(PalVersion); + + StructInfo allocatorInfo = {0}; + allocatorInfo.name = "PalAllocator"; + allocatorInfo.fields = allocatorFields; + allocatorInfo.fieldCount = ARRAY_SIZE(allocatorFields); + allocatorInfo.expected.alignof = 8; + allocatorInfo.expected.size = 24; + allocatorInfo.expected.padding = 0; + allocatorInfo.actual = STRUCT(PalAllocator); + + StructInfo loggerInfo = {0}; + loggerInfo.name = "PalLogger"; + loggerInfo.fields = loggerFields; + loggerInfo.fieldCount = ARRAY_SIZE(loggerFields); + loggerInfo.expected.alignof = 8; + loggerInfo.expected.size = 16; + loggerInfo.expected.padding = 0; + loggerInfo.actual = STRUCT(PalLogger); + + PalBool status = checkABI(&versionInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&allocatorInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&loggerInfo, flags); +} \ No newline at end of file diff --git a/tools/abi_dump/dumps.h b/tools/abi_dump/dumps.h new file mode 100644 index 00000000..81f9245f --- /dev/null +++ b/tools/abi_dump/dumps.h @@ -0,0 +1,172 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#ifndef _DUMPS_H +#define _DUMPS_H + +#include "pal2/pal_core.h" +#include + +static const char* s_FailedString = "FAILED"; +static const char* s_PassedString = "PASSED"; + +#ifdef _MSC_VER +#define ALIGNOF(type) __alignof(type) +#else +#define ALIGNOF(type) __alignof__(type) +#endif // _MSC_VER + +#define DUMP_FLAG_CORE (1u << 0) +#define DUMP_FLAG_EVENT (1u << 1) +#define DUMP_FLAG_THREAD (1u << 2) +#define DUMP_FLAG_OPENGL (1u << 3) +#define DUMP_FLAG_GRAPHICS (1u << 4) +#define DUMP_FLAG_SYSTEM (1u << 5) +#define DUMP_FLAG_VIDEO (1u << 6) +#define DUMP_FLAG_VERBOSE (1u << 7) +#define DUMP_FLAG_QUICK (1u << 8) +#define DUMP_FLAG_ALL 0x7F + +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) +#define PADDING(type) (((ALIGNOF(type) - sizeof(type)) % ALIGNOF(type))) + +typedef struct { + uint32_t offset; + uint32_t size; +} FieldBase; + +typedef struct { + uint32_t alignof; + uint32_t size; + uint32_t padding; +} StructBase; + +typedef struct { + const char* name; + FieldBase expected; + FieldBase actual; +} FieldInfo; + +typedef struct { + const char* name; + FieldInfo* fields; + uint32_t fieldCount; + StructBase expected; + StructBase actual; +} StructInfo; + +#define FIELD(type, field) ((FieldBase){offsetof(type, field), sizeof(((type*)0)->field)}) +#define STRUCT(type) ((StructBase){ALIGNOF(type), sizeof(type), PADDING(type)}) + +static PalBool checkABI( + const StructInfo* info, + uint32_t flags) +{ + // find the size of the field column + uint32_t fieldSize = 0; + const uint32_t expectedSize = 14; + const uint32_t actualSize = 8; + char seperator[256]; + PalBool passed = PAL_TRUE; + const char* result = s_PassedString; + + // clang-format off + if (info->expected.size != info->actual.size || + info->expected.alignof != info->actual.alignof || + info->expected.padding != info->actual.padding) { + passed = PAL_FALSE; + result = s_FailedString; + } + // clang-format on + + for (uint32_t i = 0; i < info->fieldCount; i++) { + FieldInfo* field = &info->fields[i]; + uint32_t size = (uint32_t)strlen(field->name); + if (size > fieldSize) { + fieldSize = size; + } + } + + fieldSize += 2; // add 6 spaces + uint32_t seperatorSize = fieldSize + expectedSize + actualSize + 4; // add 4 spaces + for (int i = 0; i < seperatorSize; i++) { + seperator[i] = '='; + } + seperator[seperatorSize] = '\0'; + + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, "Struct: %s", info->name); + } + + if (flags & DUMP_FLAG_VERBOSE) { + palLog(nullptr, ""); + palLog(nullptr, "Struct format: Property: (Expected, Actual)"); + palLog(nullptr, "Field format: (Offset, Size)"); + palLog(nullptr, ""); + + palLog(nullptr, "Size: (%03u, %03u)", info->expected.size, info->actual.size); + palLog(nullptr, "Alignment: (%u, %u)", info->expected.alignof, info->actual.alignof); + palLog(nullptr, "Padding: (%02u, %02u)", info->expected.padding, info->actual.padding); + palLog(nullptr, seperator); + + palLog( + nullptr, + "%-*s %-*s %-*s", + fieldSize, + "Field", + expectedSize, + "Expected", + actualSize, + "Actual"); + + palLog(nullptr, seperator); + } + + for (uint32_t i = 0; i < info->fieldCount; i++) { + FieldInfo* field = &info->fields[i]; + + // clang-format off + if (field->expected.offset != field->actual.offset || + field->expected.size != field->actual.size) { + passed = PAL_FALSE; + result = s_FailedString; + } + // clang-format on + + if (flags & DUMP_FLAG_VERBOSE) { + palLog( + nullptr, + "%-*s (%03u, %03u) (%03u, %03u)", + fieldSize, + field->name, + field->expected.offset, + field->expected.size, + field->actual.offset, + field->actual.size); + } + } + if (flags & DUMP_FLAG_VERBOSE) { + palLog(nullptr, seperator); + } + + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, "Status: %s", result); + palLog(nullptr, ""); + } + + return passed; +} + +PalBool coreABIDump(uint32_t flags); +PalBool eventABIDump(uint32_t flags); +PalBool threadABIDump(uint32_t flags); +PalBool systemABIDump(uint32_t flags); +PalBool videoABIDump(uint32_t flags); +PalBool openglABIDump(uint32_t flags); +PalBool graphicsABIDump(uint32_t flags); + +#endif // _DUMPS_H diff --git a/tools/abi_dump/event_abi_dump.c b/tools/abi_dump/event_abi_dump.c new file mode 100644 index 00000000..d38fa93d --- /dev/null +++ b/tools/abi_dump/event_abi_dump.c @@ -0,0 +1,77 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_event.h" + +PalBool eventABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Event ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo eventFields[] = { + {"data", {0, 8}, FIELD(PalEvent, data)}, + {"data2", {8, 8}, FIELD(PalEvent, data2)}, + {"userId", {16, 4}, FIELD(PalEvent, userId)}, + {"type", {20, 4}, FIELD(PalEvent, type)}}; + + FieldInfo eventQueueFields[] = { + {"push", {0, 8}, FIELD(PalEventQueue, push)}, + {"poll", {8, 8}, FIELD(PalEventQueue, poll)}, + {"userData", {16, 8}, FIELD(PalEventQueue, userData)}}; + + FieldInfo eventDriverCreateInfoFields[] = { + {"allocator", {0, 8}, FIELD(PalEventDriverCreateInfo, allocator)}, + {"queue", {8, 8}, FIELD(PalEventDriverCreateInfo, queue)}, + {"callback", {16, 8}, FIELD(PalEventDriverCreateInfo, callback)}, + {"userData", {24, 8}, FIELD(PalEventDriverCreateInfo, userData)}, + }; + + StructInfo eventInfo = {0}; + eventInfo.name = "PalEvent"; + eventInfo.fields = eventFields; + eventInfo.fieldCount = ARRAY_SIZE(eventFields); + eventInfo.expected.alignof = 8; + eventInfo.expected.size = 24; + eventInfo.expected.padding = 0; + eventInfo.actual = STRUCT(PalEvent); + + StructInfo eventQueueInfo = {0}; + eventQueueInfo.name = "PalEventQueue"; + eventQueueInfo.fields = eventQueueFields; + eventQueueInfo.fieldCount = ARRAY_SIZE(eventQueueFields); + eventQueueInfo.expected.alignof = 8; + eventQueueInfo.expected.size = 24; + eventQueueInfo.expected.padding = 0; + eventQueueInfo.actual = STRUCT(PalEventQueue); + + StructInfo eventDriverCreateInfo = {0}; + eventDriverCreateInfo.name = "PalEventDriverCreateInfo"; + eventDriverCreateInfo.fields = eventDriverCreateInfoFields; + eventDriverCreateInfo.fieldCount = ARRAY_SIZE(eventDriverCreateInfoFields); + eventDriverCreateInfo.expected.alignof = 8; + eventDriverCreateInfo.expected.size = 32; + eventDriverCreateInfo.expected.padding = 0; + eventDriverCreateInfo.actual = STRUCT(PalEventDriverCreateInfo); + + PalBool status = checkABI(&eventInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&eventQueueInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&eventDriverCreateInfo, flags); +} \ No newline at end of file diff --git a/tools/abi_dump/graphics_abi_dump.c b/tools/abi_dump/graphics_abi_dump.c new file mode 100644 index 00000000..a7d891ad --- /dev/null +++ b/tools/abi_dump/graphics_abi_dump.c @@ -0,0 +1,1860 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_graphics.h" + +static PalBool adapterDump(uint32_t flags) +{ + // clang-format off + FieldInfo adapterInfoFields[] = { + { "vram", {0, 8}, FIELD(PalAdapterInfo, vram) }, + { "sharedMemory", {8, 8}, FIELD(PalAdapterInfo, sharedMemory) }, + { "driverVersion", {16, 8}, FIELD(PalAdapterInfo, driverVersion) }, + { "vendorId", {24, 4}, FIELD(PalAdapterInfo, vendorId) }, + { "deviceId", {28, 4}, FIELD(PalAdapterInfo, deviceId) }, + { "shaderFormats", {32, 4}, FIELD(PalAdapterInfo, shaderFormats) }, + { "type", {36, 4}, FIELD(PalAdapterInfo, type) }, + { "apiType", {40, 4}, FIELD(PalAdapterInfo, apiType) }, + { "name", {44, 128}, FIELD(PalAdapterInfo, name) }, + { "backendName", {172, 32}, FIELD(PalAdapterInfo, backendName) }, + { "reserved", {204, 4}, FIELD(PalAdapterInfo, reserved) } + }; + + FieldInfo imageCapFields[] = { + { "maxWidth", {0, 4}, FIELD(PalImageCapabilities, maxWidth) }, + { "maxHeight", {4, 4}, FIELD(PalImageCapabilities, maxHeight) }, + { "maxDepth", {8, 4}, FIELD(PalImageCapabilities, maxDepth) }, + { "maxArrayLayers", {12, 4}, FIELD(PalImageCapabilities, maxArrayLayers) }, + { "maxMipLevels", {16, 4}, FIELD(PalImageCapabilities, maxMipLevels) } + }; + + FieldInfo resourceCapFields[] = { + { "maxPerStageSampledImages", {0, 4}, FIELD(PalResourceCapabilities, maxPerStageSampledImages) }, + { "maxPerSetSampledImages", {4, 4}, FIELD(PalResourceCapabilities, maxPerSetSampledImages) }, + { "maxPerStageStorageImages", {8, 4}, FIELD(PalResourceCapabilities, maxPerStageStorageImages) }, + { "maxPerSetStorageImages", {12, 4}, FIELD(PalResourceCapabilities, maxPerSetStorageImages) }, + { "maxPerStageSamplers", {16, 4}, FIELD(PalResourceCapabilities, maxPerStageSamplers) }, + { "maxPerSetSamplers", {20, 4}, FIELD(PalResourceCapabilities, maxPerSetSamplers) }, + { "maxPerStageStorageBuffers", {24, 4}, FIELD(PalResourceCapabilities, maxPerStageStorageBuffers) }, + { "maxPerSetStorageBuffers", {28, 4}, FIELD(PalResourceCapabilities, maxPerSetStorageBuffers) }, + { "maxPerStageUniformBuffers", {32, 4}, FIELD(PalResourceCapabilities, maxPerStageUniformBuffers) }, + { "maxPerSetUniformBuffers", {36, 4}, FIELD(PalResourceCapabilities, maxPerSetUniformBuffers) }, + { "maxPerStageAccelerationStructure", {40, 4}, FIELD(PalResourceCapabilities, maxPerStageAccelerationStructure) }, + { "maxPerSetAccelerationStructure", {44, 4}, FIELD(PalResourceCapabilities, maxPerSetAccelerationStructure) }, + { "maxBoundSets", {48, 4}, FIELD(PalResourceCapabilities, maxBoundSets) }, + }; + + FieldInfo computeCapFields[] = { + { "maxWorkGroupInvocations", {0, 4}, FIELD(PalComputeCapabilities, maxWorkGroupInvocations) }, + { "maxWorkGroupCount", {4, 12}, FIELD(PalComputeCapabilities, maxWorkGroupCount) }, + { "maxWorkGroupSize", {16, 12}, FIELD(PalComputeCapabilities, maxWorkGroupSize) } + }; + + FieldInfo viewportCapFields[] = { + { "maxWidth", {0, 4}, FIELD(PalViewportCapabilities, maxWidth) }, + { "maxHeight", {4, 4}, FIELD(PalViewportCapabilities, maxHeight) }, + { "minBoundsRange", {8, 4}, FIELD(PalViewportCapabilities, minBoundsRange) }, + { "maxBoundsRange", {12, 4}, FIELD(PalViewportCapabilities, maxBoundsRange) } + }; + + FieldInfo adapterCapFields[] = { + { "maxComputeQueues", {0, 4}, FIELD(PalAdapterCapabilities, maxComputeQueues) }, + { "maxGraphicsQueues", {4, 4}, FIELD(PalAdapterCapabilities, maxGraphicsQueues) }, + { "maxCopyQueues", {8, 4}, FIELD(PalAdapterCapabilities, maxCopyQueues) }, + { "maxColorAttachments", {12, 4}, FIELD(PalAdapterCapabilities, maxColorAttachments) }, + { "maxUniformBufferSize", {16, 4}, FIELD(PalAdapterCapabilities, maxUniformBufferSize) }, + { "maxStorageBufferSize", {20, 4}, FIELD(PalAdapterCapabilities, maxStorageBufferSize) }, + { "maxPushConstantSize", {24, 4}, FIELD(PalAdapterCapabilities, maxPushConstantSize) }, + { "maxVertexLayouts", {28, 4}, FIELD(PalAdapterCapabilities, maxVertexLayouts) }, + { "maxVertexAttributes", {32, 4}, FIELD(PalAdapterCapabilities, maxVertexAttributes) }, + { "maxTessellationPatchPoint", {36, 4}, FIELD(PalAdapterCapabilities, maxTessellationPatchPoint) }, + { "viewportCaps", {40, 16}, FIELD(PalAdapterCapabilities, viewportCaps) }, + { "imageCaps", {56, 20}, FIELD(PalAdapterCapabilities, imageCaps) }, + { "resourceCaps", {76, 52}, FIELD(PalAdapterCapabilities, resourceCaps) }, + { "computeCaps", {128, 28}, FIELD(PalAdapterCapabilities, computeCaps) } + }; + + FieldInfo formatInfoFields[] = { + { "usages", {0, 4}, FIELD(PalFormatInfo, usages) }, + { "format", {4, 4}, FIELD(PalFormatInfo, format) }, + { "sampleCount", {8, 4}, FIELD(PalFormatInfo, sampleCount) } + }; + // clang-format on + + StructInfo adapterInfo = {0}; + adapterInfo.name = "PalAdapterInfo"; + adapterInfo.fields = adapterInfoFields; + adapterInfo.fieldCount = ARRAY_SIZE(adapterInfoFields); + adapterInfo.expected.alignof = 8; + adapterInfo.expected.size = 208; + adapterInfo.expected.padding = 0; + adapterInfo.actual = STRUCT(PalAdapterInfo); + + StructInfo imageCap = {0}; + imageCap.name = "PalImageCapabilities"; + imageCap.fields = imageCapFields; + imageCap.fieldCount = ARRAY_SIZE(imageCapFields); + imageCap.expected.alignof = 4; + imageCap.expected.size = 20; + imageCap.expected.padding = 0; + imageCap.actual = STRUCT(PalImageCapabilities); + + StructInfo resourceCap = {0}; + resourceCap.name = "PalResourceCapabilities"; + resourceCap.fields = resourceCapFields; + resourceCap.fieldCount = ARRAY_SIZE(resourceCapFields); + resourceCap.expected.alignof = 4; + resourceCap.expected.size = 52; + resourceCap.expected.padding = 0; + resourceCap.actual = STRUCT(PalResourceCapabilities); + + StructInfo computeCap = {0}; + computeCap.name = "PalComputeCapabilities"; + computeCap.fields = computeCapFields; + computeCap.fieldCount = ARRAY_SIZE(computeCapFields); + computeCap.expected.alignof = 4; + computeCap.expected.size = 28; + computeCap.expected.padding = 0; + computeCap.actual = STRUCT(PalComputeCapabilities); + + StructInfo viewportCap = {0}; + viewportCap.name = "PalViewportCapabilities"; + viewportCap.fields = viewportCapFields; + viewportCap.fieldCount = ARRAY_SIZE(viewportCapFields); + viewportCap.expected.alignof = 4; + viewportCap.expected.size = 16; + viewportCap.expected.padding = 0; + viewportCap.actual = STRUCT(PalViewportCapabilities); + + StructInfo adapterCap = {0}; + adapterCap.name = "PalAdapterCapabilities"; + adapterCap.fields = adapterCapFields; + adapterCap.fieldCount = ARRAY_SIZE(adapterCapFields); + adapterCap.expected.alignof = 4; + adapterCap.expected.size = 156; + adapterCap.expected.padding = 0; + adapterCap.actual = STRUCT(PalAdapterCapabilities); + + StructInfo formatInfo = {0}; + formatInfo.name = "PalFormatInfo"; + formatInfo.fields = formatInfoFields; + formatInfo.fieldCount = ARRAY_SIZE(formatInfoFields); + formatInfo.expected.alignof = 4; + formatInfo.expected.size = 12; + formatInfo.expected.padding = 0; + formatInfo.actual = STRUCT(PalFormatInfo); + + PalBool status = checkABI(&adapterInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&imageCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&resourceCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&computeCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&viewportCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&adapterCap, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&formatInfo, flags); +} + +static PalBool deviceDump(uint32_t flags) +{ + // clang-format off + FieldInfo samplerAnisotropyCapFields[] = { + { "maxAnisotropy", {0, 4}, FIELD(PalSamplerAnisotropyCapabilities, maxAnisotropy) } + }; + + FieldInfo multiViewCapFields[] = { + { "maxViewCount", {0, 4}, FIELD(PalMultiViewCapabilities, maxViewCount) } + }; + + FieldInfo multiViewportCapFields[] = { + { "maxCount", {0, 4}, FIELD(PalMultiViewportCapabilities, maxCount) } + }; + + FieldInfo depthStencilCapFields[] = { + { "supportedDepthResolveModes", {0, 4}, FIELD(PalDepthStencilCapabilities, supportedDepthResolveModes) }, + { "supportedStencilResolveModes", {4, 4}, FIELD(PalDepthStencilCapabilities, supportedStencilResolveModes) }, + { "supportsIndependentResolve", {8, 4}, FIELD(PalDepthStencilCapabilities, supportsIndependentResolve) }, + { "supportsIndependentResolveNone", {12, 4}, FIELD(PalDepthStencilCapabilities, supportsIndependentResolveNone) } + }; + + FieldInfo fragmentShadingRateCapFields[] = { + { "supportedShadingRates", {0, 4}, FIELD(PalFragmentShadingRateCapabilities, supportedShadingRates) }, + { "supportedCombinerOps", {4, 4}, FIELD(PalFragmentShadingRateCapabilities, supportedCombinerOps) }, + { "minTexelWidth", {8, 4}, FIELD(PalFragmentShadingRateCapabilities, minTexelWidth) }, + { "minTexelHeight", {12, 4}, FIELD(PalFragmentShadingRateCapabilities, minTexelHeight) }, + { "maxTexelWidth", {16, 4}, FIELD(PalFragmentShadingRateCapabilities, maxTexelWidth) }, + { "maxTexelHeight", {20, 4}, FIELD(PalFragmentShadingRateCapabilities, maxTexelHeight) } + }; + + FieldInfo meshCapFields[] = { + { "maxOutputPrimitives", {0, 4}, FIELD(PalMeshShaderCapabilities, maxOutputPrimitives) }, + { "maxOutputVertices", {4, 4}, FIELD(PalMeshShaderCapabilities, maxOutputVertices) }, + { "maxWorkGroupInvocations", {8, 4}, FIELD(PalMeshShaderCapabilities, maxWorkGroupInvocations) }, + { "maxTaskWorkGroupInvocations", {12, 4}, FIELD(PalMeshShaderCapabilities, maxTaskWorkGroupInvocations) }, + { "maxWorkGroupCount", {16, 12}, FIELD(PalMeshShaderCapabilities, maxWorkGroupCount) }, + { "maxTaskWorkGroupCount", {28, 12}, FIELD(PalMeshShaderCapabilities, maxTaskWorkGroupCount) } + }; + + FieldInfo rayTracingCapFields[] = { + { "maxRecursionDepth", {0, 4}, FIELD(PalRayTracingCapabilities, maxRecursionDepth) }, + { "maxHitAttributeSize", {4, 4}, FIELD(PalRayTracingCapabilities, maxHitAttributeSize) }, + { "maxInstanceCount", {8, 4}, FIELD(PalRayTracingCapabilities, maxInstanceCount) }, + { "maxPrimitiveCount", {12, 4}, FIELD(PalRayTracingCapabilities, maxPrimitiveCount) }, + { "maxGeometryCount", {16, 4}, FIELD(PalRayTracingCapabilities, maxGeometryCount) }, + { "maxPayloadSize", {20, 4}, FIELD(PalRayTracingCapabilities, maxPayloadSize) }, + { "maxDispatchInvocations", {24, 4}, FIELD(PalRayTracingCapabilities, maxDispatchInvocations) } + }; + + FieldInfo descriptorIndexingCapFields[] = { + { "flags", {0, 4}, FIELD(PalDescriptorIndexingCapabilities, flags) }, + { "maxPerStageSampledImages", {4, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageSampledImages) }, + { "maxPerSetSampledImages", {8, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetSampledImages) }, + { "maxPerStageStorageImages", {12, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageStorageImages) }, + { "maxPerSetStorageImages", {16, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetStorageImages) }, + { "maxPerStageSamplers", {20, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageSamplers) }, + { "maxPerSetSamplers", {24, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetSamplers) }, + { "maxPerStageStorageBuffers", {28, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageStorageBuffers) }, + { "maxPerSetStorageBuffers", {32, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetStorageBuffers) }, + { "maxPerStageUniformBuffers", {36, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageUniformBuffers) }, + { "maxPerSetUniformBuffers", {40, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetUniformBuffers) }, + { "maxPerStageAccelerationStructure", {44, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerStageAccelerationStructure) }, + { "maxPerSetAccelerationStructure", {48, 4}, FIELD(PalDescriptorIndexingCapabilities, maxPerSetAccelerationStructure) } + }; + + FieldInfo memoryRequirementFields[] = { + { "size", {0, 8}, FIELD(PalMemoryRequirements, size) }, + { "alignment", {8, 8}, FIELD(PalMemoryRequirements, alignment) }, + { "memoryMask", {16, 8}, FIELD(PalMemoryRequirements, memoryMask) }, + { "supportedMemoryTypes", {24, 4}, FIELD(PalMemoryRequirements, supportedMemoryTypes) }, + { "reserved", {28, 4}, FIELD(PalMemoryRequirements, reserved) } + }; + + FieldInfo shaderEntryInfoFields[] = { + { "entryName", {0, 8}, FIELD(PalShaderEntryInfo, entryName) }, + { "stage", {8, 4}, FIELD(PalShaderEntryInfo, stage) }, + { "patchControlPoints", {12, 4}, FIELD(PalShaderEntryInfo, patchControlPoints) } + }; + + FieldInfo shaderCreateInfoFields[] = { + { "bcode", {0, 8}, FIELD(PalShaderCreateInfo, code) }, + { "entries", {8, 8}, FIELD(PalShaderCreateInfo, entries) }, + { "codeSize", {16, 4}, FIELD(PalShaderCreateInfo, codeSize) }, + { "entryCount", {20, 4}, FIELD(PalShaderCreateInfo, entryCount) } + }; + // clang-format on + + StructInfo samplerAnisotropyCap = {0}; + samplerAnisotropyCap.name = "PalSamplerAnisotropyCapabilities"; + samplerAnisotropyCap.fields = samplerAnisotropyCapFields; + samplerAnisotropyCap.fieldCount = ARRAY_SIZE(samplerAnisotropyCapFields); + samplerAnisotropyCap.expected.alignof = 4; + samplerAnisotropyCap.expected.size = 4; + samplerAnisotropyCap.expected.padding = 0; + samplerAnisotropyCap.actual = STRUCT(PalSamplerAnisotropyCapabilities); + + StructInfo multiViewCap = {0}; + multiViewCap.name = "PalMultiViewCapabilities"; + multiViewCap.fields = multiViewCapFields; + multiViewCap.fieldCount = ARRAY_SIZE(multiViewCapFields); + multiViewCap.expected.alignof = 4; + multiViewCap.expected.size = 4; + multiViewCap.expected.padding = 0; + multiViewCap.actual = STRUCT(PalMultiViewCapabilities); + + StructInfo multiViewportCap = {0}; + multiViewportCap.name = "PalMultiViewportCapabilities"; + multiViewportCap.fields = multiViewportCapFields; + multiViewportCap.fieldCount = ARRAY_SIZE(multiViewportCapFields); + multiViewportCap.expected.alignof = 4; + multiViewportCap.expected.size = 4; + multiViewportCap.expected.padding = 0; + multiViewportCap.actual = STRUCT(PalMultiViewportCapabilities); + + StructInfo depthStencilCap = {0}; + depthStencilCap.name = "PalDepthStencilCapabilities"; + depthStencilCap.fields = depthStencilCapFields; + depthStencilCap.fieldCount = ARRAY_SIZE(depthStencilCapFields); + depthStencilCap.expected.alignof = 4; + depthStencilCap.expected.size = 16; + depthStencilCap.expected.padding = 0; + depthStencilCap.actual = STRUCT(PalDepthStencilCapabilities); + + StructInfo fragmentShadingRateCap = {0}; + fragmentShadingRateCap.name = "PalFragmentShadingRateCapabilities"; + fragmentShadingRateCap.fields = fragmentShadingRateCapFields; + fragmentShadingRateCap.fieldCount = ARRAY_SIZE(fragmentShadingRateCapFields); + fragmentShadingRateCap.expected.alignof = 4; + fragmentShadingRateCap.expected.size = 24; + fragmentShadingRateCap.expected.padding = 0; + fragmentShadingRateCap.actual = STRUCT(PalFragmentShadingRateCapabilities); + + StructInfo meshCap = {0}; + meshCap.name = "PalMeshShaderCapabilities"; + meshCap.fields = meshCapFields; + meshCap.fieldCount = ARRAY_SIZE(meshCapFields); + meshCap.expected.alignof = 4; + meshCap.expected.size = 40; + meshCap.expected.padding = 0; + meshCap.actual = STRUCT(PalMeshShaderCapabilities); + + StructInfo rayTracingCap = {0}; + rayTracingCap.name = "PalRayTracingCapabilities"; + rayTracingCap.fields = rayTracingCapFields; + rayTracingCap.fieldCount = ARRAY_SIZE(rayTracingCapFields); + rayTracingCap.expected.alignof = 4; + rayTracingCap.expected.size = 28; + rayTracingCap.expected.padding = 0; + rayTracingCap.actual = STRUCT(PalRayTracingCapabilities); + + StructInfo descriptorIndexingCap = {0}; + descriptorIndexingCap.name = "PalDescriptorIndexingCapabilities"; + descriptorIndexingCap.fields = descriptorIndexingCapFields; + descriptorIndexingCap.fieldCount = ARRAY_SIZE(descriptorIndexingCapFields); + descriptorIndexingCap.expected.alignof = 4; + descriptorIndexingCap.expected.size = 52; + descriptorIndexingCap.expected.padding = 0; + descriptorIndexingCap.actual = STRUCT(PalDescriptorIndexingCapabilities); + + StructInfo memoryRequirement = {0}; + memoryRequirement.name = "PalMemoryRequirements"; + memoryRequirement.fields = memoryRequirementFields; + memoryRequirement.fieldCount = ARRAY_SIZE(memoryRequirementFields); + memoryRequirement.expected.alignof = 8; + memoryRequirement.expected.size = 32; + memoryRequirement.expected.padding = 0; + memoryRequirement.actual = STRUCT(PalMemoryRequirements); + + StructInfo shaderEntryInfo = {0}; + shaderEntryInfo.name = "PalShaderEntryInfo"; + shaderEntryInfo.fields = shaderEntryInfoFields; + shaderEntryInfo.fieldCount = ARRAY_SIZE(shaderEntryInfoFields); + shaderEntryInfo.expected.alignof = 8; + shaderEntryInfo.expected.size = 16; + shaderEntryInfo.expected.padding = 0; + shaderEntryInfo.actual = STRUCT(PalShaderEntryInfo); + + StructInfo shaderCreateInfo = {0}; + shaderCreateInfo.name = "PalShaderCreateInfo"; + shaderCreateInfo.fields = shaderCreateInfoFields; + shaderCreateInfo.fieldCount = ARRAY_SIZE(shaderCreateInfoFields); + shaderCreateInfo.expected.alignof = 8; + shaderCreateInfo.expected.size = 24; + shaderCreateInfo.expected.padding = 0; + shaderCreateInfo.actual = STRUCT(PalShaderCreateInfo); + + PalBool status = checkABI(&samplerAnisotropyCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&multiViewCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&multiViewportCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&depthStencilCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&fragmentShadingRateCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&meshCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&rayTracingCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorIndexingCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&memoryRequirement, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&shaderEntryInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&shaderCreateInfo, flags); +} + +static PalBool swapchainDump(uint32_t flags) +{ + // clang-format off + FieldInfo surfaceCapFields[] = { + { "supportedPresentModes", {0, 4}, FIELD(PalSurfaceCapabilities, supportedPresentModes) }, + { "supportedCompositeAlphas", {4, 4}, FIELD(PalSurfaceCapabilities, supportedCompositeAlphas) }, + { "supportedFormats", {8, 4}, FIELD(PalSurfaceCapabilities, supportedFormats) }, + { "minImageCount", {12, 4}, FIELD(PalSurfaceCapabilities, minImageCount) }, + { "maxImageCount", {16, 4}, FIELD(PalSurfaceCapabilities, maxImageCount) }, + { "minImageWidth", {20, 4}, FIELD(PalSurfaceCapabilities, minImageWidth) }, + { "minImageHeight", {24, 4}, FIELD(PalSurfaceCapabilities, minImageHeight) }, + { "maxImageWidth", {28, 4}, FIELD(PalSurfaceCapabilities, maxImageWidth) }, + { "maxImageHeight", {32, 4}, FIELD(PalSurfaceCapabilities, maxImageHeight) }, + { "maxImageArrayLayers", {36, 4}, FIELD(PalSurfaceCapabilities, maxImageArrayLayers) } + }; + + FieldInfo swapchainNextImageInfoFields[] = { + { "timeout", {0, 8}, FIELD(PalSwapchainNextImageInfo, timeout) }, + { "signalSemaphore", {8, 8}, FIELD(PalSwapchainNextImageInfo, signalSemaphore) }, + { "fence", {16, 8}, FIELD(PalSwapchainNextImageInfo, fence) } + }; + + FieldInfo swapchainCreateInfoFields[] = { + { "clipped", {0, 4}, FIELD(PalSwapchainCreateInfo, clipped) }, + { "width", {4, 4}, FIELD(PalSwapchainCreateInfo, width) }, + { "height", {8, 4}, FIELD(PalSwapchainCreateInfo, height) }, + { "imageCount", {12, 4}, FIELD(PalSwapchainCreateInfo, imageCount) }, + { "imageArrayLayerCount", {16, 4}, FIELD(PalSwapchainCreateInfo, imageArrayLayerCount) }, + { "presentMode", {20, 4}, FIELD(PalSwapchainCreateInfo, presentMode) }, + { "compositeAlpha", {24, 4}, FIELD(PalSwapchainCreateInfo, compositeAlpha) }, + { "format", {28, 4}, FIELD(PalSwapchainCreateInfo, format) } + }; + // clang-format on + + StructInfo surfaceCap = {0}; + surfaceCap.name = "PalSurfaceCapabilities"; + surfaceCap.fields = surfaceCapFields; + surfaceCap.fieldCount = ARRAY_SIZE(surfaceCapFields); + surfaceCap.expected.alignof = 4; + surfaceCap.expected.size = 40; + surfaceCap.expected.padding = 0; + surfaceCap.actual = STRUCT(PalSurfaceCapabilities); + + StructInfo swapchainNextImageInfo = {0}; + swapchainNextImageInfo.name = "PalSwapchainNextImageInfo"; + swapchainNextImageInfo.fields = swapchainNextImageInfoFields; + swapchainNextImageInfo.fieldCount = ARRAY_SIZE(swapchainNextImageInfoFields); + swapchainNextImageInfo.expected.alignof = 8; + swapchainNextImageInfo.expected.size = 24; + swapchainNextImageInfo.expected.padding = 0; + swapchainNextImageInfo.actual = STRUCT(PalSwapchainNextImageInfo); + + StructInfo swapchainCreateInfo = {0}; + swapchainCreateInfo.name = "PalSwapchainCreateInfo"; + swapchainCreateInfo.fields = swapchainCreateInfoFields; + swapchainCreateInfo.fieldCount = ARRAY_SIZE(swapchainCreateInfoFields); + swapchainCreateInfo.expected.alignof = 4; + swapchainCreateInfo.expected.size = 32; + swapchainCreateInfo.expected.padding = 0; + swapchainCreateInfo.actual = STRUCT(PalSwapchainCreateInfo); + + PalBool status = checkABI(&surfaceCap, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&swapchainNextImageInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&swapchainCreateInfo, flags); +} + +static PalBool imageDump(uint32_t flags) +{ + // clang-format off + FieldInfo imageInfoFields[] = { + { "usages", {0, 4}, FIELD(PalImageInfo, usages) }, + { "width", {4, 4}, FIELD(PalImageInfo, width) }, + { "height", {8, 4}, FIELD(PalImageInfo, height) }, + { "depth", {12, 4}, FIELD(PalImageInfo, depth) }, + { "arrayLayerCount", {16, 4}, FIELD(PalImageInfo, arrayLayerCount) }, + { "mipLevelCount", {20, 4}, FIELD(PalImageInfo, mipLevelCount) }, + { "sampleCount", {24, 4}, FIELD(PalImageInfo, sampleCount) }, + { "type", {28, 4}, FIELD(PalImageInfo, type) }, + { "format", {32, 4}, FIELD(PalImageInfo, format) }, + { "belongsToSwapchain", {36, 4}, FIELD(PalImageInfo, belongsToSwapchain) } + }; + + FieldInfo imageSubresourceRangeFields[] = { + { "aspect", {0, 4}, FIELD(PalImageSubresourceRange, aspect) }, + { "startMipLevel", {4, 4}, FIELD(PalImageSubresourceRange, startMipLevel) }, + { "mipLevelCount", {8, 4}, FIELD(PalImageSubresourceRange, mipLevelCount) }, + { "startArrayLayer", {12, 4}, FIELD(PalImageSubresourceRange, startArrayLayer) }, + { "layerArrayCount", {16, 4}, FIELD(PalImageSubresourceRange, layerArrayCount) } + }; + + FieldInfo bufferImageCopyInfoFields[] = { + { "bufferOffset", {0, 8}, FIELD(PalBufferImageCopyInfo, bufferOffset) }, + { "imageAspect", {8, 4}, FIELD(PalBufferImageCopyInfo, imageAspect) }, + { "bufferRowLength", {12, 4}, FIELD(PalBufferImageCopyInfo, bufferRowLength) }, + { "bufferImageHeight", {16, 4}, FIELD(PalBufferImageCopyInfo, bufferImageHeight) }, + { "ImageMipLevel", {20, 4}, FIELD(PalBufferImageCopyInfo, ImageMipLevel) }, + { "ImageStartArrayLayer", {24, 4}, FIELD(PalBufferImageCopyInfo, ImageStartArrayLayer) }, + { "ImageArrayLayerCount", {28, 4}, FIELD(PalBufferImageCopyInfo, ImageArrayLayerCount) }, + { "imageOffsetX", {32, 4}, FIELD(PalBufferImageCopyInfo, imageOffsetX) }, + { "imageOffsetY", {36, 4}, FIELD(PalBufferImageCopyInfo, imageOffsetY) }, + { "imageOffsetZ", {40, 4}, FIELD(PalBufferImageCopyInfo, imageOffsetZ) }, + { "imageWidth", {44, 4}, FIELD(PalBufferImageCopyInfo, imageWidth) }, + { "imageHeight", {48, 4}, FIELD(PalBufferImageCopyInfo, imageHeight) }, + { "imageDepth", {52, 4}, FIELD(PalBufferImageCopyInfo, imageDepth) } + }; + + FieldInfo imageCopyInfoFields[] = { + { "aspect", {0, 4}, FIELD(PalImageCopyInfo, aspect) }, + { "dstMipLevel", {4, 4}, FIELD(PalImageCopyInfo, dstMipLevel) }, + { "srcMipLevel", {8, 4}, FIELD(PalImageCopyInfo, srcMipLevel) }, + { "dstStartArrayLayer", {12, 4}, FIELD(PalImageCopyInfo, dstStartArrayLayer) }, + { "srcStartArrayLayer", {16, 4}, FIELD(PalImageCopyInfo, srcStartArrayLayer) }, + { "arrayLayerCount", {20, 4}, FIELD(PalImageCopyInfo, arrayLayerCount) }, + { "dstOffsetX", {24, 4}, FIELD(PalImageCopyInfo, dstOffsetX) }, + { "srcOffsetX", {28, 4}, FIELD(PalImageCopyInfo, srcOffsetX) }, + { "dstOffsetY", {32, 4}, FIELD(PalImageCopyInfo, dstOffsetY) }, + { "srcOffsetY", {36, 4}, FIELD(PalImageCopyInfo, srcOffsetY) }, + { "dstOffsetZ", {40, 4}, FIELD(PalImageCopyInfo, dstOffsetZ) }, + { "srcOffsetZ", {44, 4}, FIELD(PalImageCopyInfo, srcOffsetZ) }, + { "width", {48, 4}, FIELD(PalImageCopyInfo, width) }, + { "height", {52, 4}, FIELD(PalImageCopyInfo, height) }, + { "depth", {56, 4}, FIELD(PalImageCopyInfo, depth) } + }; + + FieldInfo imageCreateInfoFields[] = { + { "usages", {0, 4}, FIELD(PalImageCreateInfo, usages) }, + { "width", {4, 4}, FIELD(PalImageCreateInfo, width) }, + { "height", {8, 4}, FIELD(PalImageCreateInfo, height) }, + { "depth", {12, 4}, FIELD(PalImageCreateInfo, depth) }, + { "arrayLayerCount", {16, 4}, FIELD(PalImageCreateInfo, arrayLayerCount) }, + { "mipLevelCount", {20, 4}, FIELD(PalImageCreateInfo, mipLevelCount) }, + { "sampleCount", {24, 4}, FIELD(PalImageCreateInfo, sampleCount) }, + { "type", {28, 4}, FIELD(PalImageCreateInfo, type) }, + { "format", {32, 4}, FIELD(PalImageCreateInfo, format) }, + { "memoryUsage", {36, 4}, FIELD(PalImageCreateInfo, memoryUsage) } + }; + + FieldInfo imageViewCreateInfoFields[] = { + { "format", {0, 4}, FIELD(PalImageViewCreateInfo, format) }, + { "type", {4, 4}, FIELD(PalImageViewCreateInfo, type) }, + { "subresourceRange", {8, 20}, FIELD(PalImageViewCreateInfo, subresourceRange) } + }; + + FieldInfo samplerCreateInfoFields[] = { + { "enableCompare", {0, 4}, FIELD(PalSamplerCreateInfo, enableCompare) }, + { "enableAnisotropy", {4, 4}, FIELD(PalSamplerCreateInfo, enableAnisotropy) }, + { "mipLodBias", {8, 4}, FIELD(PalSamplerCreateInfo, mipLodBias) }, + { "minLod", {12, 4}, FIELD(PalSamplerCreateInfo, minLod) }, + { "maxLod", {16, 4}, FIELD(PalSamplerCreateInfo, maxLod) }, + { "maxAnisotropy", {20, 4}, FIELD(PalSamplerCreateInfo, maxAnisotropy) }, + { "minFilterMode", {24, 4}, FIELD(PalSamplerCreateInfo, minFilterMode) }, + { "magFilterMode", {28, 4}, FIELD(PalSamplerCreateInfo, magFilterMode) }, + { "mipmapMode", {32, 4}, FIELD(PalSamplerCreateInfo, mipmapMode) }, + { "addressModeU", {36, 4}, FIELD(PalSamplerCreateInfo, addressModeU) }, + { "addressModeV", {40, 4}, FIELD(PalSamplerCreateInfo, addressModeV) }, + { "addressModeW", {44, 4}, FIELD(PalSamplerCreateInfo, addressModeW) }, + { "compareOp", {48, 4}, FIELD(PalSamplerCreateInfo, compareOp) }, + { "borderColor", {52, 4}, FIELD(PalSamplerCreateInfo, borderColor) }, + }; + // clang-format on + + StructInfo imageInfo = {0}; + imageInfo.name = "PalImageInfo"; + imageInfo.fields = imageInfoFields; + imageInfo.fieldCount = ARRAY_SIZE(imageInfoFields); + imageInfo.expected.alignof = 4; + imageInfo.expected.size = 40; + imageInfo.expected.padding = 0; + imageInfo.actual = STRUCT(PalImageInfo); + + StructInfo imageSubresourceRange = {0}; + imageSubresourceRange.name = "PalImageSubresourceRange"; + imageSubresourceRange.fields = imageSubresourceRangeFields; + imageSubresourceRange.fieldCount = ARRAY_SIZE(imageSubresourceRangeFields); + imageSubresourceRange.expected.alignof = 4; + imageSubresourceRange.expected.size = 20; + imageSubresourceRange.expected.padding = 0; + imageSubresourceRange.actual = STRUCT(PalImageSubresourceRange); + + StructInfo bufferImageCopyInfo = {0}; + bufferImageCopyInfo.name = "PalBufferImageCopyInfo"; + bufferImageCopyInfo.fields = bufferImageCopyInfoFields; + bufferImageCopyInfo.fieldCount = ARRAY_SIZE(bufferImageCopyInfoFields); + bufferImageCopyInfo.expected.alignof = 8; + bufferImageCopyInfo.expected.size = 56; + bufferImageCopyInfo.expected.padding = 0; + bufferImageCopyInfo.actual = STRUCT(PalBufferImageCopyInfo); + + StructInfo imageCopyInfo = {0}; + imageCopyInfo.name = "PalImageCopyInfo"; + imageCopyInfo.fields = imageCopyInfoFields; + imageCopyInfo.fieldCount = ARRAY_SIZE(imageCopyInfoFields); + imageCopyInfo.expected.alignof = 4; + imageCopyInfo.expected.size = 60; + imageCopyInfo.expected.padding = 0; + imageCopyInfo.actual = STRUCT(PalImageCopyInfo); + + StructInfo imageCreateInfo = {0}; + imageCreateInfo.name = "PalImageCreateInfo"; + imageCreateInfo.fields = imageCreateInfoFields; + imageCreateInfo.fieldCount = ARRAY_SIZE(imageCreateInfoFields); + imageCreateInfo.expected.alignof = 4; + imageCreateInfo.expected.size = 40; + imageCreateInfo.expected.padding = 0; + imageCreateInfo.actual = STRUCT(PalImageCreateInfo); + + StructInfo imageViewCreateInfo = {0}; + imageViewCreateInfo.name = "PalImageViewCreateInfo"; + imageViewCreateInfo.fields = imageViewCreateInfoFields; + imageViewCreateInfo.fieldCount = ARRAY_SIZE(imageViewCreateInfoFields); + imageViewCreateInfo.expected.alignof = 4; + imageViewCreateInfo.expected.size = 28; + imageViewCreateInfo.expected.padding = 0; + imageViewCreateInfo.actual = STRUCT(PalImageViewCreateInfo); + + StructInfo samplerCreateInfo = {0}; + samplerCreateInfo.name = "PalSamplerCreateInfo"; + samplerCreateInfo.fields = samplerCreateInfoFields; + samplerCreateInfo.fieldCount = ARRAY_SIZE(samplerCreateInfoFields); + samplerCreateInfo.expected.alignof = 4; + samplerCreateInfo.expected.size = 56; + samplerCreateInfo.expected.padding = 0; + samplerCreateInfo.actual = STRUCT(PalSamplerCreateInfo); + + PalBool status = checkABI(&imageInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&imageSubresourceRange, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&bufferImageCopyInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&imageCopyInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&imageCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&imageViewCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&samplerCreateInfo, flags); +} + +static PalBool bufferDump(uint32_t flags) +{ + // clang-format off + FieldInfo stagingRequirementFields[] = { + { "bufferSize", {0, 8}, FIELD(PalImageStagingRequirements, bufferSize) }, + { "bufferRowLength", {8, 4}, FIELD(PalImageStagingRequirements, bufferRowLength) }, + { "bufferImageHeight", {12, 4}, FIELD(PalImageStagingRequirements, bufferImageHeight) } + }; + + FieldInfo bufferCopyInfoFields[] = { + { "size", {0, 8}, FIELD(PalBufferCopyInfo, size) }, + { "dstOffset", {8, 8}, FIELD(PalBufferCopyInfo, dstOffset) }, + { "srcOffset", {16, 8}, FIELD(PalBufferCopyInfo, srcOffset) } + }; + + FieldInfo bufferCreateInfoFields[] = { + { "size", {0, 8}, FIELD(PalBufferCreateInfo, size) }, + { "usages", {8, 4}, FIELD(PalBufferCreateInfo, usages) }, + { "memoryUsage", {12, 4}, FIELD(PalBufferCreateInfo, memoryUsage) } + }; + + FieldInfo drawIndirectDataFields[] = { + { "vertexCount", {0, 4}, FIELD(PalDrawIndirectData, vertexCount) }, + { "instanceCount", {4, 4}, FIELD(PalDrawIndirectData, instanceCount) }, + { "firstVertex", {8, 4}, FIELD(PalDrawIndirectData, firstVertex) }, + { "firstInstance", {12, 4}, FIELD(PalDrawIndirectData, firstInstance) } + }; + + FieldInfo drawIndexedIndirectDataFields[] = { + { "indexCount", {0, 4}, FIELD(PalDrawIndexedIndirectData, indexCount) }, + { "instanceCount", {4, 4}, FIELD(PalDrawIndexedIndirectData, instanceCount) }, + { "firstIndex", {8, 4}, FIELD(PalDrawIndexedIndirectData, firstIndex) }, + { "vertexOffset", {12, 4}, FIELD(PalDrawIndexedIndirectData, vertexOffset) }, + { "firstInstance", {16, 4}, FIELD(PalDrawIndexedIndirectData, firstInstance) } + }; + + FieldInfo dispatchIndirectDataFields[] = { + { "groupCountXOrWidth", {0, 4}, FIELD(PalDispatchIndirectData, groupCountXOrWidth) }, + { "groupCountXOrHeight", {4, 4}, FIELD(PalDispatchIndirectData, groupCountXOrHeight) }, + { "groupCountXOrDepth", {8, 4}, FIELD(PalDispatchIndirectData, groupCountXOrDepth) } + }; + // clang-format on + + StructInfo stagingRequirement = {0}; + stagingRequirement.name = "PalImageStagingRequirements"; + stagingRequirement.fields = stagingRequirementFields; + stagingRequirement.fieldCount = ARRAY_SIZE(stagingRequirementFields); + stagingRequirement.expected.alignof = 8; + stagingRequirement.expected.size = 16; + stagingRequirement.expected.padding = 0; + stagingRequirement.actual = STRUCT(PalImageStagingRequirements); + + StructInfo bufferCopyInfo = {0}; + bufferCopyInfo.name = "PalBufferCopyInfo"; + bufferCopyInfo.fields = bufferCopyInfoFields; + bufferCopyInfo.fieldCount = ARRAY_SIZE(bufferCopyInfoFields); + bufferCopyInfo.expected.alignof = 8; + bufferCopyInfo.expected.size = 24; + bufferCopyInfo.expected.padding = 0; + bufferCopyInfo.actual = STRUCT(PalBufferCopyInfo); + + StructInfo bufferCreateInfo = {0}; + bufferCreateInfo.name = "PalBufferCreateInfo"; + bufferCreateInfo.fields = bufferCreateInfoFields; + bufferCreateInfo.fieldCount = ARRAY_SIZE(bufferCreateInfoFields); + bufferCreateInfo.expected.alignof = 8; + bufferCreateInfo.expected.size = 16; + bufferCreateInfo.expected.padding = 0; + bufferCreateInfo.actual = STRUCT(PalBufferCreateInfo); + + StructInfo drawIndirectData = {0}; + drawIndirectData.name = "PalDrawIndirectData"; + drawIndirectData.fields = drawIndirectDataFields; + drawIndirectData.fieldCount = ARRAY_SIZE(drawIndirectDataFields); + drawIndirectData.expected.alignof = 4; + drawIndirectData.expected.size = 16; + drawIndirectData.expected.padding = 0; + drawIndirectData.actual = STRUCT(PalDrawIndirectData); + + StructInfo drawIndexedIndirectData = {0}; + drawIndexedIndirectData.name = "PalDrawIndexedIndirectData"; + drawIndexedIndirectData.fields = drawIndexedIndirectDataFields; + drawIndexedIndirectData.fieldCount = ARRAY_SIZE(drawIndexedIndirectDataFields); + drawIndexedIndirectData.expected.alignof = 4; + drawIndexedIndirectData.expected.size = 20; + drawIndexedIndirectData.expected.padding = 0; + drawIndexedIndirectData.actual = STRUCT(PalDrawIndexedIndirectData); + + StructInfo dispatchIndirectData = {0}; + dispatchIndirectData.name = "PalDispatchIndirectData"; + dispatchIndirectData.fields = dispatchIndirectDataFields; + dispatchIndirectData.fieldCount = ARRAY_SIZE(dispatchIndirectDataFields); + dispatchIndirectData.expected.alignof = 4; + dispatchIndirectData.expected.size = 12; + dispatchIndirectData.expected.padding = 0; + dispatchIndirectData.actual = STRUCT(PalDispatchIndirectData); + + PalBool status = checkABI(&stagingRequirement, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&bufferCopyInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&bufferCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&drawIndirectData, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&drawIndexedIndirectData, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&dispatchIndirectData, flags); +} + +static PalBool accelerationStructureDump(uint32_t flags) +{ + // clang-format off + FieldInfo accelerationStructureInstanceFields[] = { + { "blas", {0, 8}, FIELD(PalAccelerationStructureInstance, blas) }, + { "flags", {8, 4}, FIELD(PalAccelerationStructureInstance, flags) }, + { "mask", {12, 4}, FIELD(PalAccelerationStructureInstance, mask) }, + { "instanceId", {16, 4}, FIELD(PalAccelerationStructureInstance, instanceId) }, + { "hitGroupOffset", {20, 4}, FIELD(PalAccelerationStructureInstance, hitGroupOffset) }, + { "transform", {24, 48}, FIELD(PalAccelerationStructureInstance, transform) } + }; + + FieldInfo accelerationStructureBuildSizeFields[] = { + { "accelerationStructureSize", {0, 8}, FIELD(PalAccelerationStructureBuildSize, accelerationStructureSize) }, + { "scratchBufferSize", {8, 8}, FIELD(PalAccelerationStructureBuildSize, scratchBufferSize) }, + { "updateScratchBufferSize", {16, 8}, FIELD(PalAccelerationStructureBuildSize, updateScratchBufferSize) } + }; + + FieldInfo geometryDataTriangleFields[] = { + { "vertexBufferAddress", {0, 8}, FIELD(PalGeometryDataTriangle, vertexBufferAddress) }, + { "indexBufferAddress", {8, 8}, FIELD(PalGeometryDataTriangle, indexBufferAddress) }, + { "transformBufferAddress", {16, 8}, FIELD(PalGeometryDataTriangle, transformBufferAddress) }, + { "vertexType", {24, 4}, FIELD(PalGeometryDataTriangle, vertexType) }, + { "indexType", {28, 4}, FIELD(PalGeometryDataTriangle, indexType) }, + { "vertexCount", {32, 4}, FIELD(PalGeometryDataTriangle, vertexCount) }, + { "vertexStride", {36, 4}, FIELD(PalGeometryDataTriangle, vertexStride) } + }; + + FieldInfo geometryDataAABBSFields[] = { + { "bufferAddress", {0, 8}, FIELD(PalGeometryDataAABBS, bufferAddress) }, + { "stride", {8, 8}, FIELD(PalGeometryDataAABBS, stride) } + }; + + FieldInfo geometryFields[] = { + { "data", {0, 8}, FIELD(PalGeometry, data) }, + { "primitiveCount", {8, 8}, FIELD(PalGeometry, primitiveCount) }, + { "flags", {16, 4}, FIELD(PalGeometry, flags) }, + { "type", {20, 4}, FIELD(PalGeometry, type) }, + }; + + FieldInfo accelerationStructureBuildInfoFields[] = { + { "dst", {0, 8}, FIELD(PalAccelerationStructureBuildInfo, dst) }, + { "src", {8, 8}, FIELD(PalAccelerationStructureBuildInfo, src) }, + { "geometries", {16, 8}, FIELD(PalAccelerationStructureBuildInfo, geometries) }, + { "scratchBufferAddress", {24, 8}, FIELD(PalAccelerationStructureBuildInfo, scratchBufferAddress) }, + { "instanceBufferAddress", {32, 8}, FIELD(PalAccelerationStructureBuildInfo, instanceBufferAddress) }, + { "buildHints", {40, 4}, FIELD(PalAccelerationStructureBuildInfo, buildHints) }, + { "type", {44, 4}, FIELD(PalAccelerationStructureBuildInfo, type) }, + { "buildMode", {48, 4}, FIELD(PalAccelerationStructureBuildInfo, buildMode) }, + { "count", {52, 4}, FIELD(PalAccelerationStructureBuildInfo, count) }, + }; + + FieldInfo accelerationStructureCreateInfoFields[] = { + { "buffer", {0, 8}, FIELD(PalAccelerationStructureCreateInfo, buffer) }, + { "offset", {8, 8}, FIELD(PalAccelerationStructureCreateInfo, offset) }, + { "size", {16, 8}, FIELD(PalAccelerationStructureCreateInfo, size) }, + { "type", {24, 4}, FIELD(PalAccelerationStructureCreateInfo, type) }, + { "reserved", {28, 4}, FIELD(PalAccelerationStructureCreateInfo, reserved) } + }; + // clang-format on + + StructInfo accelerationStructureInstance = {0}; + accelerationStructureInstance.name = "PalAccelerationStructureInstance"; + accelerationStructureInstance.fields = accelerationStructureInstanceFields; + accelerationStructureInstance.fieldCount = ARRAY_SIZE(accelerationStructureInstanceFields); + accelerationStructureInstance.expected.alignof = 8; + accelerationStructureInstance.expected.size = 72; + accelerationStructureInstance.expected.padding = 0; + accelerationStructureInstance.actual = STRUCT(PalAccelerationStructureInstance); + + StructInfo accelerationStructureBuildSize = {0}; + accelerationStructureBuildSize.name = "PalAccelerationStructureBuildSize"; + accelerationStructureBuildSize.fields = accelerationStructureBuildSizeFields; + accelerationStructureBuildSize.fieldCount = ARRAY_SIZE(accelerationStructureBuildSizeFields); + accelerationStructureBuildSize.expected.alignof = 8; + accelerationStructureBuildSize.expected.size = 24; + accelerationStructureBuildSize.expected.padding = 0; + accelerationStructureBuildSize.actual = STRUCT(PalAccelerationStructureBuildSize); + + StructInfo geometryDataTriangle = {0}; + geometryDataTriangle.name = "PalGeometryDataTriangle"; + geometryDataTriangle.fields = geometryDataTriangleFields; + geometryDataTriangle.fieldCount = ARRAY_SIZE(geometryDataTriangleFields); + geometryDataTriangle.expected.alignof = 8; + geometryDataTriangle.expected.size = 40; + geometryDataTriangle.expected.padding = 0; + geometryDataTriangle.actual = STRUCT(PalGeometryDataTriangle); + + StructInfo geometryDataAABBS = {0}; + geometryDataAABBS.name = "PalGeometryDataAABBS"; + geometryDataAABBS.fields = geometryDataAABBSFields; + geometryDataAABBS.fieldCount = ARRAY_SIZE(geometryDataAABBSFields); + geometryDataAABBS.expected.alignof = 8; + geometryDataAABBS.expected.size = 16; + geometryDataAABBS.expected.padding = 0; + geometryDataAABBS.actual = STRUCT(PalGeometryDataAABBS); + + StructInfo geometry = {0}; + geometry.name = "PalGeometry"; + geometry.fields = geometryFields; + geometry.fieldCount = ARRAY_SIZE(geometryFields); + geometry.expected.alignof = 8; + geometry.expected.size = 24; + geometry.expected.padding = 0; + geometry.actual = STRUCT(PalGeometry); + + StructInfo accelerationStructureBuildInfo = {0}; + accelerationStructureBuildInfo.name = "PalAccelerationStructureBuildInfo"; + accelerationStructureBuildInfo.fields = accelerationStructureBuildInfoFields; + accelerationStructureBuildInfo.fieldCount = ARRAY_SIZE(accelerationStructureBuildInfoFields); + accelerationStructureBuildInfo.expected.alignof = 8; + accelerationStructureBuildInfo.expected.size = 56; + accelerationStructureBuildInfo.expected.padding = 0; + accelerationStructureBuildInfo.actual = STRUCT(PalAccelerationStructureBuildInfo); + + StructInfo accelerationStructureCreateInfo = {0}; + accelerationStructureCreateInfo.name = "PalAccelerationStructureCreateInfo"; + accelerationStructureCreateInfo.fields = accelerationStructureCreateInfoFields; + accelerationStructureCreateInfo.fieldCount = ARRAY_SIZE(accelerationStructureCreateInfoFields); + accelerationStructureCreateInfo.expected.alignof = 8; + accelerationStructureCreateInfo.expected.size = 32; + accelerationStructureCreateInfo.expected.padding = 0; + accelerationStructureCreateInfo.actual = STRUCT(PalAccelerationStructureCreateInfo); + + PalBool status = checkABI(&accelerationStructureInstance, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&accelerationStructureBuildSize, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&geometryDataTriangle, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&geometryDataAABBS, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&geometry, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&accelerationStructureBuildInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&accelerationStructureCreateInfo, flags); +} + +static PalBool pipelineDump(uint32_t flags) +{ + // clang-format off + FieldInfo vertexAttributeFields[] = { + { "semanticID", {0, 4}, FIELD(PalVertexAttribute, semanticID) }, + { "type", {4, 4}, FIELD(PalVertexAttribute, type) } + }; + + FieldInfo vertexLayoutFields[] = { + { "attributes", {0, 8}, FIELD(PalVertexLayout, attributes) }, + { "attributeCount", {8, 4}, FIELD(PalVertexLayout, attributeCount) }, + { "type", {12, 4}, FIELD(PalVertexLayout, type) }, + { "binding", {16, 4}, FIELD(PalVertexLayout, binding) }, + { "reserved", {20, 4}, FIELD(PalVertexLayout, reserved) } + }; + + FieldInfo rasterizerStateFields[] = { + { "enableDepthClamp", {0, 4}, FIELD(PalRasterizerState, enableDepthClamp) }, + { "enableDepthBias", {4, 4}, FIELD(PalRasterizerState, enableDepthBias) }, + { "depthBiasConstant", {8, 4}, FIELD(PalRasterizerState, depthBiasConstant) }, + { "depthBiasSlope", {12, 4}, FIELD(PalRasterizerState, depthBiasSlope) }, + { "depthBiasClamp", {16, 4}, FIELD(PalRasterizerState, depthBiasClamp) }, + { "polygonMode", {20, 4}, FIELD(PalRasterizerState, polygonMode) }, + { "cullMode", {24, 4}, FIELD(PalRasterizerState, cullMode) }, + { "frontFace", {28, 4}, FIELD(PalRasterizerState, frontFace) } + }; + + FieldInfo multisampleStateFields[] = { + { "sampleMask", {0, 8}, FIELD(PalMultisampleState, sampleMask) }, + { "enableSampleShading", {8, 4}, FIELD(PalMultisampleState, enableSampleShading) }, + { "enableAlphaToCoverage", {12, 4}, FIELD(PalMultisampleState, enableAlphaToCoverage) }, + { "sampleCount", {16, 4}, FIELD(PalMultisampleState, sampleCount) }, + { "minSampleShading", {20, 4}, FIELD(PalMultisampleState, minSampleShading) } + }; + + FieldInfo stencilOpStateFields[] = { + { "failOp", {0, 4}, FIELD(PalStencilOpState, failOp) }, + { "passOp", {4, 4}, FIELD(PalStencilOpState, passOp) }, + { "depthFailOp", {8, 4}, FIELD(PalStencilOpState, depthFailOp) }, + { "compareOp", {12, 4}, FIELD(PalStencilOpState, compareOp) } + }; + + FieldInfo depthStencilStateFields[] = { + { "enableDepthTest", {0, 4}, FIELD(PalDepthStencilState, enableDepthTest) }, + { "enableDepthWrite", {4, 4}, FIELD(PalDepthStencilState, enableDepthWrite) }, + { "enableStencilTest", {8, 4}, FIELD(PalDepthStencilState, enableStencilTest) }, + { "compareOp", {12, 4}, FIELD(PalDepthStencilState, compareOp) }, + { "frontStencilOpState", {16, 16}, FIELD(PalDepthStencilState, frontStencilOpState) }, + { "backStencilOpState", {32, 16}, FIELD(PalDepthStencilState, backStencilOpState) }, + }; + + FieldInfo colorBlendAttachmentFields[] = { + { "enableBlend", {0, 4}, FIELD(PalColorBlendAttachment, enableBlend) }, + { "colorWriteMask", {4, 4}, FIELD(PalColorBlendAttachment, colorWriteMask) }, + { "dstColorBlendFactor", {8, 4}, FIELD(PalColorBlendAttachment, dstColorBlendFactor) }, + { "srcColorBlendFactor", {12, 4}, FIELD(PalColorBlendAttachment, srcColorBlendFactor) }, + { "colorBlendOp", {16, 4}, FIELD(PalColorBlendAttachment, colorBlendOp) }, + { "dstAlphaBlendFactor", {20, 4}, FIELD(PalColorBlendAttachment, dstAlphaBlendFactor) }, + { "srcAlphaBlendFactor", {24, 4}, FIELD(PalColorBlendAttachment, srcAlphaBlendFactor) }, + { "alphaBlendOp", {28, 4}, FIELD(PalColorBlendAttachment, alphaBlendOp) } + }; + + FieldInfo fragmentShadingRateStateFields[] = { + { "rate", {0, 4}, FIELD(PalFragmentShadingRateState, rate) }, + { "combinerOps", {4, 8}, FIELD(PalFragmentShadingRateState, combinerOps) } + }; + + FieldInfo pushConstantInfoFields[] = { + { "offset", {0, 4}, FIELD(PalPushConstantInfo, offset) }, + { "size", {4, 4}, FIELD(PalPushConstantInfo, size) } + }; + + FieldInfo pipelineLayoutCreateInfoFields[] = { + { "descriptorSetLayouts", {0, 8}, FIELD(PalPipelineLayoutCreateInfo, descriptorSetLayouts) }, + { "pushConstantInfo", {8, 8}, FIELD(PalPipelineLayoutCreateInfo, pushConstantInfo) }, + { "descriptorSetLayoutCount", {16, 4}, FIELD(PalPipelineLayoutCreateInfo, descriptorSetLayoutCount) }, + { "usePushConstant", {20, 4}, FIELD(PalPipelineLayoutCreateInfo, usePushConstant) } + }; + + FieldInfo graphicsPipelineCreateInfoFields[] = { + { "pipelineLayout", {0, 8}, FIELD(PalGraphicsPipelineCreateInfo, pipelineLayout) }, + { "shaders", {8, 8}, FIELD(PalGraphicsPipelineCreateInfo, shaders) }, + { "vertexLayouts", {16, 8}, FIELD(PalGraphicsPipelineCreateInfo, vertexLayouts) }, + { "colorBlendAttachments", {24, 8}, FIELD(PalGraphicsPipelineCreateInfo, colorBlendAttachments) }, + { "rasterizerState", {32, 8}, FIELD(PalGraphicsPipelineCreateInfo, rasterizerState) }, + { "multisampleState", {40, 8}, FIELD(PalGraphicsPipelineCreateInfo, multisampleState) }, + { "depthStencilState", {48, 8}, FIELD(PalGraphicsPipelineCreateInfo, depthStencilState) }, + { "fragmentShadingRateState", {56, 8}, FIELD(PalGraphicsPipelineCreateInfo, fragmentShadingRateState) }, + { "renderingLayout", {64, 8}, FIELD(PalGraphicsPipelineCreateInfo, renderingLayout) }, + { "primitiveRestartEnable", {72, 4}, FIELD(PalGraphicsPipelineCreateInfo, primitiveRestartEnable) }, + { "vertexLayoutCount", {76, 4}, FIELD(PalGraphicsPipelineCreateInfo, vertexLayoutCount) }, + { "colorBlendAttachmentCount", {80, 4}, FIELD(PalGraphicsPipelineCreateInfo, colorBlendAttachmentCount) }, + { "shaderCount", {84, 4}, FIELD(PalGraphicsPipelineCreateInfo, shaderCount) }, + { "indexType", {88, 4}, FIELD(PalGraphicsPipelineCreateInfo, indexType) }, + { "topology", {92, 4}, FIELD(PalGraphicsPipelineCreateInfo, topology) } + }; + + FieldInfo computePipelineCreateInfoFields[] = { + { "pipelineLayout", {0, 8}, FIELD(PalComputePipelineCreateInfo, pipelineLayout) }, + { "computeShader", {8, 8}, FIELD(PalComputePipelineCreateInfo, computeShader) } + }; + + FieldInfo rayTracingShaderGroupCreateInfoFields[] = { + { "type", {0, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, type) }, + { "anyHitShaderIndex", {4, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, anyHitShaderIndex) }, + { "anyHitShaderEntryIndex", {8, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, anyHitShaderEntryIndex) }, + { "closestHitShaderIndex", {12, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, closestHitShaderIndex) }, + { "closestHitShaderEntryIndex", {16, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, closestHitShaderEntryIndex) }, + { "generalShaderIndex", {20, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, generalShaderIndex) }, + { "generalShaderEntryIndex", {24, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, generalShaderEntryIndex) }, + { "intersectionShaderIndex", {28, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, intersectionShaderIndex) }, + { "intersectionShaderEntryIndex", {32, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, intersectionShaderEntryIndex) }, + { "maxDataSize", {36, 4}, FIELD(PalRayTracingShaderGroupCreateInfo, maxDataSize) } + }; + + FieldInfo rayTracingPipelineCreateInfoFields[] = { + { "pipelineLayout", {0, 8}, FIELD(PalRayTracingPipelineCreateInfo, pipelineLayout) }, + { "shaderGroups", {8, 8}, FIELD(PalRayTracingPipelineCreateInfo, shaderGroups) }, + { "shaders", {16, 8}, FIELD(PalRayTracingPipelineCreateInfo, shaders) }, + { "shaderGroupCount", {24, 4}, FIELD(PalRayTracingPipelineCreateInfo, shaderGroupCount) }, + { "shaderCount", {28, 4}, FIELD(PalRayTracingPipelineCreateInfo, shaderCount) }, + { "maxRecursionDepth", {32, 4}, FIELD(PalRayTracingPipelineCreateInfo, maxRecursionDepth) }, + { "maxAttributeSize", {36, 4}, FIELD(PalRayTracingPipelineCreateInfo, maxAttributeSize) }, + { "maxPayloadSize", {40, 4}, FIELD(PalRayTracingPipelineCreateInfo, maxPayloadSize) }, + { "reserved", {44, 4}, FIELD(PalRayTracingPipelineCreateInfo, reserved) } + }; + // clang-format on + + StructInfo vertexAttribute = {0}; + vertexAttribute.name = "PalVertexAttribute"; + vertexAttribute.fields = vertexAttributeFields; + vertexAttribute.fieldCount = ARRAY_SIZE(vertexAttributeFields); + vertexAttribute.expected.alignof = 4; + vertexAttribute.expected.size = 8; + vertexAttribute.expected.padding = 0; + vertexAttribute.actual = STRUCT(PalVertexAttribute); + + StructInfo vertexLayout = {0}; + vertexLayout.name = "PalVertexLayout"; + vertexLayout.fields = vertexLayoutFields; + vertexLayout.fieldCount = ARRAY_SIZE(vertexLayoutFields); + vertexLayout.expected.alignof = 8; + vertexLayout.expected.size = 24; + vertexLayout.expected.padding = 0; + vertexLayout.actual = STRUCT(PalVertexLayout); + + StructInfo rasterizerState = {0}; + rasterizerState.name = "PalRasterizerState"; + rasterizerState.fields = rasterizerStateFields; + rasterizerState.fieldCount = ARRAY_SIZE(rasterizerStateFields); + rasterizerState.expected.alignof = 4; + rasterizerState.expected.size = 32; + rasterizerState.expected.padding = 0; + rasterizerState.actual = STRUCT(PalRasterizerState); + + StructInfo multisampleState = {0}; + multisampleState.name = "PalMultisampleState"; + multisampleState.fields = multisampleStateFields; + multisampleState.fieldCount = ARRAY_SIZE(multisampleStateFields); + multisampleState.expected.alignof = 8; + multisampleState.expected.size = 24; + multisampleState.expected.padding = 0; + multisampleState.actual = STRUCT(PalMultisampleState); + + StructInfo stencilOpState = {0}; + stencilOpState.name = "PalStencilOpState"; + stencilOpState.fields = stencilOpStateFields; + stencilOpState.fieldCount = ARRAY_SIZE(stencilOpStateFields); + stencilOpState.expected.alignof = 4; + stencilOpState.expected.size = 16; + stencilOpState.expected.padding = 0; + stencilOpState.actual = STRUCT(PalStencilOpState); + + StructInfo depthStencilState = {0}; + depthStencilState.name = "PalDepthStencilState"; + depthStencilState.fields = depthStencilStateFields; + depthStencilState.fieldCount = ARRAY_SIZE(depthStencilStateFields); + depthStencilState.expected.alignof = 4; + depthStencilState.expected.size = 48; + depthStencilState.expected.padding = 0; + depthStencilState.actual = STRUCT(PalDepthStencilState); + + StructInfo colorBlendAttachment = {0}; + colorBlendAttachment.name = "PalColorBlendAttachment"; + colorBlendAttachment.fields = colorBlendAttachmentFields; + colorBlendAttachment.fieldCount = ARRAY_SIZE(colorBlendAttachmentFields); + colorBlendAttachment.expected.alignof = 4; + colorBlendAttachment.expected.size = 32; + colorBlendAttachment.expected.padding = 0; + colorBlendAttachment.actual = STRUCT(PalColorBlendAttachment); + + StructInfo fragmentShadingRateState = {0}; + fragmentShadingRateState.name = "PalFragmentShadingRateState"; + fragmentShadingRateState.fields = fragmentShadingRateStateFields; + fragmentShadingRateState.fieldCount = ARRAY_SIZE(fragmentShadingRateStateFields); + fragmentShadingRateState.expected.alignof = 4; + fragmentShadingRateState.expected.size = 12; + fragmentShadingRateState.expected.padding = 0; + fragmentShadingRateState.actual = STRUCT(PalFragmentShadingRateState); + + StructInfo pushConstantInfo = {0}; + pushConstantInfo.name = "PalPushConstantInfo"; + pushConstantInfo.fields = pushConstantInfoFields; + pushConstantInfo.fieldCount = ARRAY_SIZE(pushConstantInfoFields); + pushConstantInfo.expected.alignof = 4; + pushConstantInfo.expected.size = 8; + pushConstantInfo.expected.padding = 0; + pushConstantInfo.actual = STRUCT(PalPushConstantInfo); + + StructInfo pipelineLayoutCreateInfo = {0}; + pipelineLayoutCreateInfo.name = "PalPipelineLayoutCreateInfo"; + pipelineLayoutCreateInfo.fields = pipelineLayoutCreateInfoFields; + pipelineLayoutCreateInfo.fieldCount = ARRAY_SIZE(pipelineLayoutCreateInfoFields); + pipelineLayoutCreateInfo.expected.alignof = 8; + pipelineLayoutCreateInfo.expected.size = 24; + pipelineLayoutCreateInfo.expected.padding = 0; + pipelineLayoutCreateInfo.actual = STRUCT(PalPipelineLayoutCreateInfo); + + StructInfo graphicsPipelineCreateInfo = {0}; + graphicsPipelineCreateInfo.name = "PalGraphicsPipelineCreateInfo"; + graphicsPipelineCreateInfo.fields = graphicsPipelineCreateInfoFields; + graphicsPipelineCreateInfo.fieldCount = ARRAY_SIZE(graphicsPipelineCreateInfoFields); + graphicsPipelineCreateInfo.expected.alignof = 8; + graphicsPipelineCreateInfo.expected.size = 96; + graphicsPipelineCreateInfo.expected.padding = 0; + graphicsPipelineCreateInfo.actual = STRUCT(PalGraphicsPipelineCreateInfo); + + StructInfo computePipelineCreateInfo = {0}; + computePipelineCreateInfo.name = "PalComputePipelineCreateInfo"; + computePipelineCreateInfo.fields = computePipelineCreateInfoFields; + computePipelineCreateInfo.fieldCount = ARRAY_SIZE(computePipelineCreateInfoFields); + computePipelineCreateInfo.expected.alignof = 8; + computePipelineCreateInfo.expected.size = 16; + computePipelineCreateInfo.expected.padding = 0; + computePipelineCreateInfo.actual = STRUCT(PalComputePipelineCreateInfo); + + StructInfo rayTracingShaderGroupCreateInfo = {0}; + rayTracingShaderGroupCreateInfo.name = "PalRayTracingShaderGroupCreateInfo"; + rayTracingShaderGroupCreateInfo.fields = rayTracingShaderGroupCreateInfoFields; + rayTracingShaderGroupCreateInfo.fieldCount = ARRAY_SIZE(rayTracingShaderGroupCreateInfoFields); + rayTracingShaderGroupCreateInfo.expected.alignof = 4; + rayTracingShaderGroupCreateInfo.expected.size = 40; + rayTracingShaderGroupCreateInfo.expected.padding = 0; + rayTracingShaderGroupCreateInfo.actual = STRUCT(PalRayTracingShaderGroupCreateInfo); + + StructInfo rayTracingPipelineCreateInfo = {0}; + rayTracingPipelineCreateInfo.name = "PalRayTracingPipelineCreateInfo"; + rayTracingPipelineCreateInfo.fields = rayTracingPipelineCreateInfoFields; + rayTracingPipelineCreateInfo.fieldCount = ARRAY_SIZE(rayTracingPipelineCreateInfoFields); + rayTracingPipelineCreateInfo.expected.alignof = 8; + rayTracingPipelineCreateInfo.expected.size = 48; + rayTracingPipelineCreateInfo.expected.padding = 0; + rayTracingPipelineCreateInfo.actual = STRUCT(PalRayTracingPipelineCreateInfo); + + PalBool status = checkABI(&vertexAttribute, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&vertexLayout, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&rasterizerState, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&multisampleState, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&stencilOpState, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&depthStencilState, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&colorBlendAttachment, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&fragmentShadingRateState, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&pushConstantInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&pipelineLayoutCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&graphicsPipelineCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&computePipelineCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&rayTracingShaderGroupCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&rayTracingPipelineCreateInfo, flags); +} + +static PalBool descriptorDump(uint32_t flags) +{ + // clang-format off + FieldInfo descriptorSetLayoutBindingFields[] = { + { "descriptorCount", {0, 4}, FIELD(PalDescriptorSetLayoutBinding, descriptorCount) }, + { "descriptorType", {4, 4}, FIELD(PalDescriptorSetLayoutBinding, descriptorType) } + }; + + FieldInfo descriptorPoolBindingSizeFields[] = { + { "bindingCount", {0, 4}, FIELD(PalDescriptorPoolBindingSize, bindingCount) }, + { "descriptorType", {4, 4}, FIELD(PalDescriptorPoolBindingSize, descriptorType) } + }; + + FieldInfo descriptorBufferInfoFields[] = { + { "buffer", {0, 8}, FIELD(PalDescriptorBufferInfo, buffer) }, + { "offset", {8, 8}, FIELD(PalDescriptorBufferInfo, offset) }, + { "size", {16, 8}, FIELD(PalDescriptorBufferInfo, size) }, + { "stride", {24, 8}, FIELD(PalDescriptorBufferInfo, stride) } + }; + + FieldInfo descriptorImageViewInfoFields[] = { + { "imageView", {0, 8}, FIELD(PalDescriptorImageViewInfo, imageView) } + }; + + FieldInfo descriptorSamplerInfoFields[] = { + { "sampler", {0, 8}, FIELD(PalDescriptorSamplerInfo, sampler) } + }; + + FieldInfo descriptorTLASInfoFields[] = { + { "tlas", {0, 8}, FIELD(PalDescriptorTLASInfo, tlas) } + }; + + FieldInfo descriptorSetWriteInfoFields[] = { + { "descriptorSet", {0, 8}, FIELD(PalDescriptorSetWriteInfo, descriptorSet) }, + { "bufferInfos", {8, 8}, FIELD(PalDescriptorSetWriteInfo, bufferInfos) }, + { "imageViewInfos", {16, 8}, FIELD(PalDescriptorSetWriteInfo, imageViewInfos) }, + { "samplerInfos", {24, 8}, FIELD(PalDescriptorSetWriteInfo, samplerInfos) }, + { "tlasInfos", {32, 8}, FIELD(PalDescriptorSetWriteInfo, tlasInfos) }, + { "descriptorType", {40, 4}, FIELD(PalDescriptorSetWriteInfo, descriptorType) }, + { "layoutBindingIndex", {44, 4}, FIELD(PalDescriptorSetWriteInfo, layoutBindingIndex) }, + { "arrayElement", {48, 4}, FIELD(PalDescriptorSetWriteInfo, arrayElement) }, + { "descriptorCount", {52, 4}, FIELD(PalDescriptorSetWriteInfo, descriptorCount) } + }; + + FieldInfo descriptorSetLayoutCreateInfoFields[] = { + { "bindings", {0, 8}, FIELD(PalDescriptorSetLayoutCreateInfo, bindings) }, + { "flags", {8, 4}, FIELD(PalDescriptorSetLayoutCreateInfo, flags) }, + { "bindingCount", {12, 4}, FIELD(PalDescriptorSetLayoutCreateInfo, bindingCount) } + }; + + FieldInfo descriptorPoolCreateInfoFields[] = { + { "bindingSizes", {0, 8}, FIELD(PalDescriptorPoolCreateInfo, bindingSizes) }, + { "bindingSizeCount", {8, 4}, FIELD(PalDescriptorPoolCreateInfo, bindingSizeCount) }, + { "maxDescriptorSets", {12, 4}, FIELD(PalDescriptorPoolCreateInfo, maxDescriptorSets) }, + { "flags", {16, 4}, FIELD(PalDescriptorPoolCreateInfo, flags) }, + { "reserved", {20, 4}, FIELD(PalDescriptorPoolCreateInfo, reserved) } + }; + // clang-format on + + StructInfo descriptorSetLayoutBinding = {0}; + descriptorSetLayoutBinding.name = "PalDescriptorSetLayoutBinding"; + descriptorSetLayoutBinding.fields = descriptorSetLayoutBindingFields; + descriptorSetLayoutBinding.fieldCount = ARRAY_SIZE(descriptorSetLayoutBindingFields); + descriptorSetLayoutBinding.expected.alignof = 4; + descriptorSetLayoutBinding.expected.size = 8; + descriptorSetLayoutBinding.expected.padding = 0; + descriptorSetLayoutBinding.actual = STRUCT(PalDescriptorSetLayoutBinding); + + StructInfo descriptorPoolBindingSize = {0}; + descriptorPoolBindingSize.name = "PalDescriptorPoolBindingSize"; + descriptorPoolBindingSize.fields = descriptorPoolBindingSizeFields; + descriptorPoolBindingSize.fieldCount = ARRAY_SIZE(descriptorPoolBindingSizeFields); + descriptorPoolBindingSize.expected.alignof = 4; + descriptorPoolBindingSize.expected.size = 8; + descriptorPoolBindingSize.expected.padding = 0; + descriptorPoolBindingSize.actual = STRUCT(PalDescriptorPoolBindingSize); + + StructInfo descriptorBufferInfo = {0}; + descriptorBufferInfo.name = "PalDescriptorBufferInfo"; + descriptorBufferInfo.fields = descriptorBufferInfoFields; + descriptorBufferInfo.fieldCount = ARRAY_SIZE(descriptorBufferInfoFields); + descriptorBufferInfo.expected.alignof = 8; + descriptorBufferInfo.expected.size = 32; + descriptorBufferInfo.expected.padding = 0; + descriptorBufferInfo.actual = STRUCT(PalDescriptorBufferInfo); + + StructInfo descriptorImageViewInfo = {0}; + descriptorImageViewInfo.name = "PalDescriptorImageViewInfo"; + descriptorImageViewInfo.fields = descriptorImageViewInfoFields; + descriptorImageViewInfo.fieldCount = ARRAY_SIZE(descriptorImageViewInfoFields); + descriptorImageViewInfo.expected.alignof = 8; + descriptorImageViewInfo.expected.size = 8; + descriptorImageViewInfo.expected.padding = 0; + descriptorImageViewInfo.actual = STRUCT(PalDescriptorImageViewInfo); + + StructInfo descriptorSamplerInfo = {0}; + descriptorSamplerInfo.name = "PalDescriptorSamplerInfo"; + descriptorSamplerInfo.fields = descriptorSamplerInfoFields; + descriptorSamplerInfo.fieldCount = ARRAY_SIZE(descriptorSamplerInfoFields); + descriptorSamplerInfo.expected.alignof = 8; + descriptorSamplerInfo.expected.size = 8; + descriptorSamplerInfo.expected.padding = 0; + descriptorSamplerInfo.actual = STRUCT(PalDescriptorSamplerInfo); + + StructInfo descriptorTLASInfo = {0}; + descriptorTLASInfo.name = "PalDescriptorTLASInfo"; + descriptorTLASInfo.fields = descriptorSamplerInfoFields; + descriptorTLASInfo.fieldCount = ARRAY_SIZE(descriptorSamplerInfoFields); + descriptorTLASInfo.expected.alignof = 8; + descriptorTLASInfo.expected.size = 8; + descriptorTLASInfo.expected.padding = 0; + descriptorTLASInfo.actual = STRUCT(PalDescriptorTLASInfo); + + StructInfo descriptorSetWriteInfo = {0}; + descriptorSetWriteInfo.name = "PalDescriptorSetWriteInfo"; + descriptorSetWriteInfo.fields = descriptorSetWriteInfoFields; + descriptorSetWriteInfo.fieldCount = ARRAY_SIZE(descriptorSetWriteInfoFields); + descriptorSetWriteInfo.expected.alignof = 8; + descriptorSetWriteInfo.expected.size = 56; + descriptorSetWriteInfo.expected.padding = 0; + descriptorSetWriteInfo.actual = STRUCT(PalDescriptorSetWriteInfo); + + StructInfo descriptorSetLayoutCreateInfo = {0}; + descriptorSetLayoutCreateInfo.name = "PalDescriptorSetLayoutCreateInfo"; + descriptorSetLayoutCreateInfo.fields = descriptorSetLayoutCreateInfoFields; + descriptorSetLayoutCreateInfo.fieldCount = ARRAY_SIZE(descriptorSetLayoutCreateInfoFields); + descriptorSetLayoutCreateInfo.expected.alignof = 8; + descriptorSetLayoutCreateInfo.expected.size = 16; + descriptorSetLayoutCreateInfo.expected.padding = 0; + descriptorSetLayoutCreateInfo.actual = STRUCT(PalDescriptorSetLayoutCreateInfo); + + StructInfo descriptorPoolCreateInfo = {0}; + descriptorPoolCreateInfo.name = "PalDescriptorPoolCreateInfo"; + descriptorPoolCreateInfo.fields = descriptorPoolCreateInfoFields; + descriptorPoolCreateInfo.fieldCount = ARRAY_SIZE(descriptorPoolCreateInfoFields); + descriptorPoolCreateInfo.expected.alignof = 8; + descriptorPoolCreateInfo.expected.size = 24; + descriptorPoolCreateInfo.expected.padding = 0; + descriptorPoolCreateInfo.actual = STRUCT(PalDescriptorPoolCreateInfo); + + PalBool status = checkABI(&descriptorSetLayoutBinding, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorPoolBindingSize, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorBufferInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorImageViewInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorSamplerInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorTLASInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorSetWriteInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&descriptorSetLayoutCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&descriptorPoolCreateInfo, flags); +} + +static PalBool sbtDump(uint32_t flags) +{ + // clang-format off + FieldInfo shaderBindingTableRecordInfoFields[] = { + { "localData", {0, 8}, FIELD(PalShaderBindingTableRecordInfo, localData) }, + { "groupIndex", {8, 4}, FIELD(PalShaderBindingTableRecordInfo, groupIndex) }, + { "localDataSize", {12, 4}, FIELD(PalShaderBindingTableRecordInfo, localDataSize) } + }; + + FieldInfo shaderBindingTableCreateInfoFields[] = { + { "records", {0, 8}, FIELD(PalShaderBindingTableCreateInfo, records) }, + { "rayTracingPipeline", {8, 8}, FIELD(PalShaderBindingTableCreateInfo, rayTracingPipeline) }, + { "recordCount", {16, 4}, FIELD(PalShaderBindingTableCreateInfo, recordCount) }, + { "reserved", {20, 4}, FIELD(PalShaderBindingTableCreateInfo, reserved) } + }; + // clang-format on + + StructInfo shaderBindingTableRecordInfo = {0}; + shaderBindingTableRecordInfo.name = "PalShaderBindingTableRecordInfo"; + shaderBindingTableRecordInfo.fields = shaderBindingTableRecordInfoFields; + shaderBindingTableRecordInfo.fieldCount = ARRAY_SIZE(shaderBindingTableRecordInfoFields); + shaderBindingTableRecordInfo.expected.alignof = 8; + shaderBindingTableRecordInfo.expected.size = 16; + shaderBindingTableRecordInfo.expected.padding = 0; + shaderBindingTableRecordInfo.actual = STRUCT(PalShaderBindingTableRecordInfo); + + StructInfo shaderBindingTableCreateInfo = {0}; + shaderBindingTableCreateInfo.name = "PalShaderBindingTableCreateInfo"; + shaderBindingTableCreateInfo.fields = shaderBindingTableCreateInfoFields; + shaderBindingTableCreateInfo.fieldCount = ARRAY_SIZE(shaderBindingTableCreateInfoFields); + shaderBindingTableCreateInfo.expected.alignof = 8; + shaderBindingTableCreateInfo.expected.size = 24; + shaderBindingTableCreateInfo.expected.padding = 0; + shaderBindingTableCreateInfo.actual = STRUCT(PalShaderBindingTableCreateInfo); + + PalBool status = checkABI(&shaderBindingTableRecordInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&shaderBindingTableCreateInfo, flags); +} + +static PalBool commandPoolDump(uint32_t flags) +{ + // clang-format off + FieldInfo commandBufferSubmitInfoFields[] = { + { "waitValue", {0, 8}, FIELD(PalCommandBufferSubmitInfo, waitValue) }, + { "signalValue", {8, 8}, FIELD(PalCommandBufferSubmitInfo, signalValue) }, + { "cmdBuffer", {16, 8}, FIELD(PalCommandBufferSubmitInfo, cmdBuffer) }, + { "waitSemaphore", {24, 8}, FIELD(PalCommandBufferSubmitInfo, waitSemaphore) }, + { "signalSemaphore", {32, 8}, FIELD(PalCommandBufferSubmitInfo, signalSemaphore) }, + { "fence", {40, 8}, FIELD(PalCommandBufferSubmitInfo, fence) }, + { "waitStages", {48, 4}, FIELD(PalCommandBufferSubmitInfo, waitStages) }, + { "signalStages", {52, 4}, FIELD(PalCommandBufferSubmitInfo, signalStages) } + }; + // clang-format on + + StructInfo commandBufferSubmitInfo = {0}; + commandBufferSubmitInfo.name = "PalCommandBufferSubmitInfo"; + commandBufferSubmitInfo.fields = commandBufferSubmitInfoFields; + commandBufferSubmitInfo.fieldCount = ARRAY_SIZE(commandBufferSubmitInfoFields); + commandBufferSubmitInfo.expected.alignof = 8; + commandBufferSubmitInfo.expected.size = 56; + commandBufferSubmitInfo.expected.padding = 0; + commandBufferSubmitInfo.actual = STRUCT(PalCommandBufferSubmitInfo); + + return checkABI(&commandBufferSubmitInfo, flags); +} + +static PalBool commandsDump(uint32_t flags) +{ + // clang-format off + FieldInfo clearValueFields[] = { + { "color", {0, 16}, FIELD(PalClearValue, color) }, + { "depth", {16, 4}, FIELD(PalClearValue, depth) }, + { "stencil", {20, 4}, FIELD(PalClearValue, stencil) } + }; + + FieldInfo attachmentDescFields[] = { + { "imageView", {0, 8}, FIELD(PalAttachmentDesc, imageView) }, + { "resolveImageView", {8, 8}, FIELD(PalAttachmentDesc, resolveImageView) }, + { "loadOp", {16, 4}, FIELD(PalAttachmentDesc, loadOp) }, + { "storeOp", {20, 4}, FIELD(PalAttachmentDesc, storeOp) }, + { "stencilLoadOp", {24, 4}, FIELD(PalAttachmentDesc, stencilLoadOp) }, + { "stencilStoreOp", {28, 4}, FIELD(PalAttachmentDesc, stencilStoreOp) }, + { "resolveMode", {32, 4}, FIELD(PalAttachmentDesc, resolveMode) }, + { "stencilResolveMode", {36, 4}, FIELD(PalAttachmentDesc, stencilResolveMode) }, + { "clearValue", {40, 24}, FIELD(PalAttachmentDesc, clearValue) }, + }; + + FieldInfo viewportFields[] = { + { "x", {0, 4}, FIELD(PalViewport, x) }, + { "y", {4, 4}, FIELD(PalViewport, y) }, + { "width", {8, 4}, FIELD(PalViewport, width) }, + { "height", {12, 4}, FIELD(PalViewport, height) }, + { "minDepth", {16, 4}, FIELD(PalViewport, minDepth) }, + { "maxDepth", {20, 4}, FIELD(PalViewport, maxDepth) } + }; + + FieldInfo rect2DFields[] = { + { "x", {0, 4}, FIELD(PalRect2D, x) }, + { "y", {4, 4}, FIELD(PalRect2D, y) }, + { "width", {8, 4}, FIELD(PalRect2D, width) }, + { "height", {12, 4}, FIELD(PalRect2D, height) }, + }; + + FieldInfo renderingInfoFields[] = { + { "colorAttachments", {0, 8}, FIELD(PalRenderingInfo, colorAttachments) }, + { "depthStencilAttachment", {8, 8}, FIELD(PalRenderingInfo, depthStencilAttachment) }, + { "fragmentShadingRateImageView", {16, 8}, FIELD(PalRenderingInfo, fragmentShadingRateImageView) }, + { "renderArea", {24, 16}, FIELD(PalRenderingInfo, renderArea) }, + { "flags", {40, 4}, FIELD(PalRenderingInfo, flags) }, + { "fragmentShadingRateTexelWidth", {44, 4}, FIELD(PalRenderingInfo, fragmentShadingRateTexelWidth) }, + { "fragmentShadingRateTexelHeight", {48, 4}, FIELD(PalRenderingInfo, fragmentShadingRateTexelHeight) }, + { "viewCount", {52, 4}, FIELD(PalRenderingInfo, viewCount) }, + { "arrayLayerCount", {56, 4}, FIELD(PalRenderingInfo, arrayLayerCount) }, + { "colorAttachentCount", {60, 4}, FIELD(PalRenderingInfo, colorAttachentCount) } + }; + + FieldInfo renderingLayoutInfoFields[] = { + { "colorAttachmentsFormat", {0, 8}, FIELD(PalRenderingLayoutInfo, colorAttachmentsFormat) }, + { "colorAttachentCount", {8, 4}, FIELD(PalRenderingLayoutInfo, colorAttachentCount) }, + { "viewCount", {12, 4}, FIELD(PalRenderingLayoutInfo, viewCount) }, + { "sampleCount", {16, 4}, FIELD(PalRenderingLayoutInfo, sampleCount) }, + { "flags", {20, 4}, FIELD(PalRenderingLayoutInfo, flags) }, + { "depthStencilAttachmentFormat", {24, 4}, FIELD(PalRenderingLayoutInfo, depthStencilAttachmentFormat) }, + { "fragmentShadingRateAttachmentFormat", {28, 4}, FIELD(PalRenderingLayoutInfo, fragmentShadingRateAttachmentFormat) } + }; + + FieldInfo workGroupBuildDataFields[] = { + { "workCount", {0, 12}, FIELD(PalWorkGroupBuildData, workCount) }, + { "workGroupSize", {12, 12}, FIELD(PalWorkGroupBuildData, workGroupSize) }, + { "workGroupCount", {24, 12}, FIELD(PalWorkGroupBuildData, workGroupCount) } + }; + + FieldInfo workGroupInfoFields[] = { + { "workGroupBase", {0, 12}, FIELD(PalWorkGroupInfo, workGroupBase) }, + { "workGroupCount", {12, 12}, FIELD(PalWorkGroupInfo, workGroupCount) } + }; + + FieldInfo barrierInfoFields[] = { + { "oldState", {0, 4}, FIELD(PalBarrierInfo, oldState) }, + { "newState", {4, 4}, FIELD(PalBarrierInfo, newState) }, + { "srcStages", {8, 4}, FIELD(PalBarrierInfo, srcStages) }, + { "dstStages", {12, 4}, FIELD(PalBarrierInfo, dstStages) } + }; + // clang-format on + + StructInfo clearValue = {0}; + clearValue.name = "PalClearValue"; + clearValue.fields = clearValueFields; + clearValue.fieldCount = ARRAY_SIZE(clearValueFields); + clearValue.expected.alignof = 4; + clearValue.expected.size = 24; + clearValue.expected.padding = 0; + clearValue.actual = STRUCT(PalClearValue); + + StructInfo attachmentDesc = {0}; + attachmentDesc.name = "PalAttachmentDesc"; + attachmentDesc.fields = attachmentDescFields; + attachmentDesc.fieldCount = ARRAY_SIZE(attachmentDescFields); + attachmentDesc.expected.alignof = 8; + attachmentDesc.expected.size = 64; + attachmentDesc.expected.padding = 0; + attachmentDesc.actual = STRUCT(PalAttachmentDesc); + + StructInfo viewport = {0}; + viewport.name = "PalViewport"; + viewport.fields = viewportFields; + viewport.fieldCount = ARRAY_SIZE(viewportFields); + viewport.expected.alignof = 4; + viewport.expected.size = 24; + viewport.expected.padding = 0; + viewport.actual = STRUCT(PalViewport); + + StructInfo rect2D = {0}; + rect2D.name = "PalRect2D"; + rect2D.fields = rect2DFields; + rect2D.fieldCount = ARRAY_SIZE(rect2DFields); + rect2D.expected.alignof = 4; + rect2D.expected.size = 16; + rect2D.expected.padding = 0; + rect2D.actual = STRUCT(PalRect2D); + + StructInfo renderingInfo = {0}; + renderingInfo.name = "PalRenderingInfo"; + renderingInfo.fields = renderingInfoFields; + renderingInfo.fieldCount = ARRAY_SIZE(renderingInfoFields); + renderingInfo.expected.alignof = 8; + renderingInfo.expected.size = 64; + renderingInfo.expected.padding = 0; + renderingInfo.actual = STRUCT(PalRenderingInfo); + + StructInfo renderingLayoutInfo = {0}; + renderingLayoutInfo.name = "PalRenderingLayoutInfo"; + renderingLayoutInfo.fields = renderingLayoutInfoFields; + renderingLayoutInfo.fieldCount = ARRAY_SIZE(renderingLayoutInfoFields); + renderingLayoutInfo.expected.alignof = 8; + renderingLayoutInfo.expected.size = 32; + renderingLayoutInfo.expected.padding = 0; + renderingLayoutInfo.actual = STRUCT(PalRenderingLayoutInfo); + + StructInfo workGroupBuildData = {0}; + workGroupBuildData.name = "PalWorkGroupBuildData"; + workGroupBuildData.fields = workGroupBuildDataFields; + workGroupBuildData.fieldCount = ARRAY_SIZE(workGroupBuildDataFields); + workGroupBuildData.expected.alignof = 4; + workGroupBuildData.expected.size = 36; + workGroupBuildData.expected.padding = 0; + workGroupBuildData.actual = STRUCT(PalWorkGroupBuildData); + + StructInfo workGroupInfo = {0}; + workGroupInfo.name = "PalWorkGroupInfo"; + workGroupInfo.fields = workGroupInfoFields; + workGroupInfo.fieldCount = ARRAY_SIZE(workGroupInfoFields); + workGroupInfo.expected.alignof = 4; + workGroupInfo.expected.size = 24; + workGroupInfo.expected.padding = 0; + workGroupInfo.actual = STRUCT(PalWorkGroupInfo); + + StructInfo barrierInfo = {0}; + barrierInfo.name = "PalBarrierInfo"; + barrierInfo.fields = barrierInfoFields; + barrierInfo.fieldCount = ARRAY_SIZE(barrierInfoFields); + barrierInfo.expected.alignof = 4; + barrierInfo.expected.size = 16; + barrierInfo.expected.padding = 0; + barrierInfo.actual = STRUCT(PalBarrierInfo); + + PalBool status = checkABI(&clearValue, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&attachmentDesc, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&viewport, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&rect2D, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&renderingInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&renderingLayoutInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&workGroupBuildData, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&workGroupInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&barrierInfo, flags); +} + +PalBool graphicsABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Graphics ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + // clang-format off + FieldInfo graphicsDebuggerFields[] = { + { "userData", {0, 8}, FIELD(PalGraphicsDebugger, userData) }, + { "callback", {8, 8}, FIELD(PalGraphicsDebugger, callback) }, + { "enableGPUValidation", {16, 4}, FIELD(PalGraphicsDebugger, enableGPUValidation) }, + { "denyGeneral", {20, 4}, FIELD(PalGraphicsDebugger, denyGeneral) }, + { "denyValidation", {24, 4}, FIELD(PalGraphicsDebugger, denyValidation) }, + { "denyPerformance", {28, 4}, FIELD(PalGraphicsDebugger, denyPerformance) }, + { "denyInfoSeverity", {32, 4}, FIELD(PalGraphicsDebugger, denyInfoSeverity) }, + { "denyWarningSeverity", {36, 4}, FIELD(PalGraphicsDebugger, denyWarningSeverity) }, + { "denyErrorSeverity", {40, 4}, FIELD(PalGraphicsDebugger, denyErrorSeverity) }, + { "reserved", {44, 4}, FIELD(PalGraphicsDebugger, reserved) } + }; + + FieldInfo graphicsBackendInfoFields[] = { + { "vtable", {0, 8}, FIELD(PalGraphicsBackendInfo, vtable) }, + { "version", {8, 4}, FIELD(PalGraphicsBackendInfo, version) }, + { "reserved", {12, 4}, FIELD(PalGraphicsBackendInfo, reserved) } + }; + // clang-format on + + StructInfo graphicsDebugger = {0}; + graphicsDebugger.name = "PalGraphicsDebugger"; + graphicsDebugger.fields = graphicsDebuggerFields; + graphicsDebugger.fieldCount = ARRAY_SIZE(graphicsDebuggerFields); + graphicsDebugger.expected.alignof = 8; + graphicsDebugger.expected.size = 48; + graphicsDebugger.expected.padding = 0; + graphicsDebugger.actual = STRUCT(PalGraphicsDebugger); + + StructInfo graphicsBackendInfo = {0}; + graphicsBackendInfo.name = "PalGraphicsBackendInfo"; + graphicsBackendInfo.fields = graphicsBackendInfoFields; + graphicsBackendInfo.fieldCount = ARRAY_SIZE(graphicsBackendInfoFields); + graphicsBackendInfo.expected.alignof = 8; + graphicsBackendInfo.expected.size = 16; + graphicsBackendInfo.expected.padding = 0; + graphicsBackendInfo.actual = STRUCT(PalGraphicsBackendInfo); + + PalBool status = checkABI(&graphicsDebugger, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&graphicsBackendInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = adapterDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = deviceDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = swapchainDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = imageDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = bufferDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = accelerationStructureDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = pipelineDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = descriptorDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = sbtDump(flags); + if (status == PAL_FALSE) { + return status; + } + + status = commandPoolDump(flags); + if (status == PAL_FALSE) { + return status; + } + + return commandsDump(flags); +} \ No newline at end of file diff --git a/tools/abi_dump/opengl_abi_dump.c b/tools/abi_dump/opengl_abi_dump.c new file mode 100644 index 00000000..e3a6c5eb --- /dev/null +++ b/tools/abi_dump/opengl_abi_dump.c @@ -0,0 +1,113 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_opengl.h" + +PalBool openglABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Opengl ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo glInfoFields[] = { + {"extensions", {0, 8}, FIELD(PalGLInfo, extensions)}, + {"major", {8, 4}, FIELD(PalGLInfo, major)}, + {"minor", {12, 4}, FIELD(PalGLInfo, minor)}, + {"backend", {16, 4}, FIELD(PalGLInfo, backend)}, + {"api", {20, 4}, FIELD(PalGLInfo, api)}, + {"vendor", {24, 32}, FIELD(PalGLInfo, vendor)}, + {"graphicsCard", {56, 64}, FIELD(PalGLInfo, graphicsCard)}, + {"version", {120, 64}, FIELD(PalGLInfo, version)}}; + + FieldInfo fbConfigFields[] = { + {"doubleBuffer", {0, 4}, FIELD(PalGLFBConfig, doubleBuffer)}, + {"stereo", {4, 4}, FIELD(PalGLFBConfig, stereo)}, + {"sRGB", {8, 4}, FIELD(PalGLFBConfig, sRGB)}, + {"index", {12, 2}, FIELD(PalGLFBConfig, index)}, + {"redBits", {14, 2}, FIELD(PalGLFBConfig, redBits)}, + {"greenBits", {16, 2}, FIELD(PalGLFBConfig, greenBits)}, + {"blueBits", {18, 2}, FIELD(PalGLFBConfig, blueBits)}, + {"alphaBits", {20, 2}, FIELD(PalGLFBConfig, alphaBits)}, + {"depthBits", {22, 2}, FIELD(PalGLFBConfig, depthBits)}, + {"stencilBits", {24, 2}, FIELD(PalGLFBConfig, stencilBits)}, + {"samples", {26, 2}, FIELD(PalGLFBConfig, samples)}}; + + FieldInfo windowFields[] = { + {"instance", {0, 8}, FIELD(PalGLWindow, instance)}, + {"window", {8, 8}, FIELD(PalGLWindow, window)}}; + + FieldInfo contextCreateInfoFields[] = { + {"window", {0, 8}, FIELD(PalGLContextCreateInfo, window)}, + {"fbConfig", {8, 8}, FIELD(PalGLContextCreateInfo, fbConfig)}, + {"shareContext", {16, 8}, FIELD(PalGLContextCreateInfo, shareContext)}, + {"profile", {24, 4}, FIELD(PalGLContextCreateInfo, profile)}, + {"reset", {28, 4}, FIELD(PalGLContextCreateInfo, reset)}, + {"release", {32, 4}, FIELD(PalGLContextCreateInfo, release)}, + {"forward", {36, 4}, FIELD(PalGLContextCreateInfo, forward)}, + {"noError", {40, 4}, FIELD(PalGLContextCreateInfo, noError)}, + {"debug", {44, 4}, FIELD(PalGLContextCreateInfo, debug)}, + {"major", {48, 4}, FIELD(PalGLContextCreateInfo, major)}, + {"minor", {52, 4}, FIELD(PalGLContextCreateInfo, minor)}}; + + StructInfo glInfo = {0}; + glInfo.name = "PalGLInfo"; + glInfo.fields = glInfoFields; + glInfo.fieldCount = ARRAY_SIZE(glInfoFields); + glInfo.expected.alignof = 8; + glInfo.expected.size = 184; + glInfo.expected.padding = 0; + glInfo.actual = STRUCT(PalGLInfo); + + StructInfo fbConfig = {0}; + fbConfig.name = "PalGLFBConfig"; + fbConfig.fields = fbConfigFields; + fbConfig.fieldCount = ARRAY_SIZE(fbConfigFields); + fbConfig.expected.alignof = 4; + fbConfig.expected.size = 28; + fbConfig.expected.padding = 0; + fbConfig.actual = STRUCT(PalGLFBConfig); + + StructInfo window = {0}; + window.name = "PalGLWindow"; + window.fields = windowFields; + window.fieldCount = ARRAY_SIZE(windowFields); + window.expected.alignof = 8; + window.expected.size = 16; + window.expected.padding = 0; + window.actual = STRUCT(PalGLWindow); + + StructInfo contextCreateInfo = {0}; + contextCreateInfo.name = "PalGLContextCreateInfo"; + contextCreateInfo.fields = contextCreateInfoFields; + contextCreateInfo.fieldCount = ARRAY_SIZE(contextCreateInfoFields); + contextCreateInfo.expected.alignof = 8; + contextCreateInfo.expected.size = 56; + contextCreateInfo.expected.padding = 0; + contextCreateInfo.actual = STRUCT(PalGLContextCreateInfo); + + PalBool status = checkABI(&glInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&fbConfig, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&window, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&contextCreateInfo, flags); +} \ No newline at end of file diff --git a/tools/abi_dump/system_abi_dump.c b/tools/abi_dump/system_abi_dump.c new file mode 100644 index 00000000..1eee93ab --- /dev/null +++ b/tools/abi_dump/system_abi_dump.c @@ -0,0 +1,64 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_system.h" + +PalBool systemABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "System ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo platformInfoFields[] = { + {"type", {0, 4}, FIELD(PalPlatformInfo, type)}, + {"apiType", {4, 4}, FIELD(PalPlatformInfo, apiType)}, + {"totalMemory", {8, 4}, FIELD(PalPlatformInfo, totalMemory)}, + {"totalRAM", {12, 4}, FIELD(PalPlatformInfo, totalRAM)}, + {"version", {16, 12}, FIELD(PalPlatformInfo, version)}, + {"name", {28, 32}, FIELD(PalPlatformInfo, name)}}; + + FieldInfo cpuInfoFields[] = { + {"features", {0, 8}, FIELD(PalCPUInfo, features)}, + {"architecture", {8, 4}, FIELD(PalCPUInfo, architecture)}, + {"numCores", {12, 4}, FIELD(PalCPUInfo, numCores)}, + {"cacheL1", {16, 4}, FIELD(PalCPUInfo, cacheL1)}, + {"cacheL2", {20, 4}, FIELD(PalCPUInfo, cacheL2)}, + {"cacheL3", {24, 4}, FIELD(PalCPUInfo, cacheL3)}, + {"numLogicalProcessors", {28, 4}, FIELD(PalCPUInfo, numLogicalProcessors)}, + {"vendor", {32, 16}, FIELD(PalCPUInfo, vendor)}, + {"model", {48, 64}, FIELD(PalCPUInfo, model)}}; + + StructInfo platformInfo = {0}; + platformInfo.name = "PalPlatformInfo"; + platformInfo.fields = platformInfoFields; + platformInfo.fieldCount = ARRAY_SIZE(platformInfoFields); + platformInfo.expected.alignof = 4; + platformInfo.expected.size = 60; + platformInfo.expected.padding = 0; + platformInfo.actual = STRUCT(PalPlatformInfo); + + StructInfo cpuInfo = {0}; + cpuInfo.name = "PalCPUInfo"; + cpuInfo.fields = cpuInfoFields; + cpuInfo.fieldCount = ARRAY_SIZE(cpuInfoFields); + cpuInfo.expected.alignof = 8; + cpuInfo.expected.size = 112; + cpuInfo.expected.padding = 0; + cpuInfo.actual = STRUCT(PalCPUInfo); + + PalBool status = checkABI(&platformInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&cpuInfo, flags); +} \ No newline at end of file diff --git a/tools/abi_dump/thread_abi_dump.c b/tools/abi_dump/thread_abi_dump.c new file mode 100644 index 00000000..ca3c335d --- /dev/null +++ b/tools/abi_dump/thread_abi_dump.c @@ -0,0 +1,37 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_thread.h" + +PalBool threadABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Thread ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo threadCreateInfoFields[] = { + {"stackSize", {0, 8}, FIELD(PalThreadCreateInfo, stackSize)}, + {"allocator", {8, 8}, FIELD(PalThreadCreateInfo, allocator)}, + {"entry", {16, 8}, FIELD(PalThreadCreateInfo, entry)}, + {"arg", {24, 8}, FIELD(PalThreadCreateInfo, arg)}}; + + StructInfo threadCreateInfo = {0}; + threadCreateInfo.name = "PalThreadCreateInfo"; + threadCreateInfo.fields = threadCreateInfoFields; + threadCreateInfo.fieldCount = ARRAY_SIZE(threadCreateInfoFields); + threadCreateInfo.expected.alignof = 8; + threadCreateInfo.expected.size = 32; + threadCreateInfo.expected.padding = 0; + threadCreateInfo.actual = STRUCT(PalThreadCreateInfo); + + return checkABI(&threadCreateInfo, flags); +} \ No newline at end of file diff --git a/tools/abi_dump/video_abi_dump.c b/tools/abi_dump/video_abi_dump.c new file mode 100644 index 00000000..9456f8ac --- /dev/null +++ b/tools/abi_dump/video_abi_dump.c @@ -0,0 +1,170 @@ + +/** + PAL - Prime Abstraction Layer + Copyright (C) 2025 + Licensed under the Zlib license. See LICENSE file in root. + */ + +#include "dumps.h" +#include "pal2/pal_video.h" + +PalBool videoABIDump(uint32_t flags) +{ + if (!(flags & DUMP_FLAG_QUICK)) { + palLog(nullptr, ""); + palLog(nullptr, "==========================================="); + palLog(nullptr, "Video ABI Dump"); + palLog(nullptr, "==========================================="); + palLog(nullptr, ""); + } + + FieldInfo monitorInfoFields[] = { + {"x", {0, 4}, FIELD(PalMonitorInfo, x)}, + {"y", {4, 4}, FIELD(PalMonitorInfo, y)}, + {"width", {8, 4}, FIELD(PalMonitorInfo, width)}, + {"height", {12, 4}, FIELD(PalMonitorInfo, height)}, + {"dpi", {16, 4}, FIELD(PalMonitorInfo, dpi)}, + {"refreshRate", {20, 4}, FIELD(PalMonitorInfo, refreshRate)}, + {"orientation", {24, 4}, FIELD(PalMonitorInfo, orientation)}, + {"primary", {28, 4}, FIELD(PalMonitorInfo, primary)}, + {"name", {32, 32}, FIELD(PalMonitorInfo, name)}}; + + FieldInfo monitorModeFields[] = { + {"bpp", {0, 4}, FIELD(PalMonitorMode, bpp)}, + {"refreshRate", {4, 4}, FIELD(PalMonitorMode, refreshRate)}, + {"width", {8, 4}, FIELD(PalMonitorMode, width)}, + {"height", {12, 4}, FIELD(PalMonitorMode, height)}}; + + FieldInfo flashInfoFields[] = { + {"flags", {0, 4}, FIELD(PalFlashInfo, flags)}, + {"interval", {4, 4}, FIELD(PalFlashInfo, interval)}, + {"count", {8, 4}, FIELD(PalFlashInfo, count)}}; + + FieldInfo iconCreateInfoFields[] = { + {"pixels", {0, 8}, FIELD(PalIconCreateInfo, pixels)}, + {"width", {8, 4}, FIELD(PalIconCreateInfo, width)}, + {"height", {12, 4}, FIELD(PalIconCreateInfo, height)}}; + + FieldInfo cursorCreateInfoFields[] = { + {"pixels", {0, 8}, FIELD(PalCursorCreateInfo, pixels)}, + {"width", {8, 4}, FIELD(PalCursorCreateInfo, width)}, + {"height", {12, 4}, FIELD(PalCursorCreateInfo, height)}, + {"xHotspot", {16, 4}, FIELD(PalCursorCreateInfo, xHotspot)}, + {"yHotspot", {20, 4}, FIELD(PalCursorCreateInfo, yHotspot)}}; + + FieldInfo windowHandleInfoFields[] = { + {"nativeInstance", {0, 8}, FIELD(PalWindowHandleInfo, nativeInstance)}, + {"nativeWindow", {8, 8}, FIELD(PalWindowHandleInfo, nativeWindow)}, + {"nativeHandle1", {16, 8}, FIELD(PalWindowHandleInfo, nativeHandle1)}, + {"nativeHandle2", {24, 8}, FIELD(PalWindowHandleInfo, nativeHandle2)}, + {"nativeHandle3", {32, 8}, FIELD(PalWindowHandleInfo, nativeHandle3)}}; + + FieldInfo windowCreateInfoFields[] = { + {"title", {0, 8}, FIELD(PalWindowCreateInfo, title)}, + {"monitor", {8, 8}, FIELD(PalWindowCreateInfo, monitor)}, + {"appName", {16, 8}, FIELD(PalWindowCreateInfo, appName)}, + {"instanceName", {24, 8}, FIELD(PalWindowCreateInfo, instanceName)}, + {"fbConfigBackend", {32, 4}, FIELD(PalWindowCreateInfo, fbConfigBackend)}, + {"fbConfigIndex", {36, 4}, FIELD(PalWindowCreateInfo, fbConfigIndex)}, + {"width", {40, 4}, FIELD(PalWindowCreateInfo, width)}, + {"height", {44, 4}, FIELD(PalWindowCreateInfo, height)}, + {"show", {48, 4}, FIELD(PalWindowCreateInfo, show)}, + {"style", {52, 4}, FIELD(PalWindowCreateInfo, style)}, + {"state", {56, 4}, FIELD(PalWindowCreateInfo, state)}, + {"center", {60, 4}, FIELD(PalWindowCreateInfo, center)}}; + + StructInfo monitorInfo = {0}; + monitorInfo.name = "PalMonitorInfo"; + monitorInfo.fields = monitorInfoFields; + monitorInfo.fieldCount = ARRAY_SIZE(monitorInfoFields); + monitorInfo.expected.alignof = 4; + monitorInfo.expected.size = 64; + monitorInfo.expected.padding = 0; + monitorInfo.actual = STRUCT(PalMonitorInfo); + + StructInfo monitorMode = {0}; + monitorMode.name = "PalMonitorMode"; + monitorMode.fields = monitorModeFields; + monitorMode.fieldCount = ARRAY_SIZE(monitorModeFields); + monitorMode.expected.alignof = 4; + monitorMode.expected.size = 16; + monitorMode.expected.padding = 0; + monitorMode.actual = STRUCT(PalMonitorMode); + + StructInfo flashInfo = {0}; + flashInfo.name = "PalFlashInfo"; + flashInfo.fields = flashInfoFields; + flashInfo.fieldCount = ARRAY_SIZE(flashInfoFields); + flashInfo.expected.alignof = 4; + flashInfo.expected.size = 12; + flashInfo.expected.padding = 0; + flashInfo.actual = STRUCT(PalFlashInfo); + + StructInfo iconCreateInfo = {0}; + iconCreateInfo.name = "PalIconCreateInfo"; + iconCreateInfo.fields = iconCreateInfoFields; + iconCreateInfo.fieldCount = ARRAY_SIZE(iconCreateInfoFields); + iconCreateInfo.expected.alignof = 8; + iconCreateInfo.expected.size = 16; + iconCreateInfo.expected.padding = 0; + iconCreateInfo.actual = STRUCT(PalIconCreateInfo); + + StructInfo cursorCreateInfo = {0}; + cursorCreateInfo.name = "PalCursorCreateInfo"; + cursorCreateInfo.fields = cursorCreateInfoFields; + cursorCreateInfo.fieldCount = ARRAY_SIZE(cursorCreateInfoFields); + cursorCreateInfo.expected.alignof = 8; + cursorCreateInfo.expected.size = 24; + cursorCreateInfo.expected.padding = 0; + cursorCreateInfo.actual = STRUCT(PalCursorCreateInfo); + + StructInfo windowHandleInfo = {0}; + windowHandleInfo.name = "PalWindowHandleInfo"; + windowHandleInfo.fields = windowHandleInfoFields; + windowHandleInfo.fieldCount = ARRAY_SIZE(windowHandleInfoFields); + windowHandleInfo.expected.alignof = 8; + windowHandleInfo.expected.size = 40; + windowHandleInfo.expected.padding = 0; + windowHandleInfo.actual = STRUCT(PalWindowHandleInfo); + + StructInfo windowCreateInfo = {0}; + windowCreateInfo.name = "PalWindowCreateInfo"; + windowCreateInfo.fields = windowCreateInfoFields; + windowCreateInfo.fieldCount = ARRAY_SIZE(windowCreateInfoFields); + windowCreateInfo.expected.alignof = 8; + windowCreateInfo.expected.size = 64; + windowCreateInfo.expected.padding = 0; + windowCreateInfo.actual = STRUCT(PalWindowCreateInfo); + + PalBool status = checkABI(&monitorInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&monitorMode, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&flashInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&iconCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&cursorCreateInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + status = checkABI(&windowHandleInfo, flags); + if (status == PAL_FALSE) { + return status; + } + + return checkABI(&windowCreateInfo, flags); +} \ No newline at end of file