Repository: krOoze/Hello_Triangle Branch: master Commit: ed984cde2cce Files: 28 Total size: 268.4 KB Directory structure: gitextract_0sir708e/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── buildCI.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc/ │ └── synchronizationTutorial.md └── src/ ├── CompilerMessages.h ├── EnumerateScheme.h ├── ErrorHandling.h ├── ExtensionLoader.h ├── HelloTriangle.cpp ├── LeanWindowsEnvironment.h ├── Vertex.h ├── VulkanEnvironment.h ├── VulkanIntrospection.h ├── WSI/ │ ├── Glfw.h │ ├── Wayland.h │ ├── Win32.h │ ├── Xcb.h │ ├── Xlib.h │ └── private/ │ ├── xdg-shell-client-protocol-private.inl │ └── xdg-shell-client-protocol.h ├── Wsi.h └── shaders/ ├── hello_triangle.frag └── hello_triangle.vert ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6NRLHTNEP8GH8&item_name=For+Petr+Kraus+%28aka+krOoze%29+if+you+appreciate+any+of+my+work+done+for+free.¤cy_code=USD&source=url', 'https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6NRLHTNEP8GH8&item_name=For+Petr+Kraus+%28aka+krOoze%29+if+you+appreciate+any+of+my+work+done+for+free.¤cy_code=EUR&source=url'] ================================================ FILE: .github/workflows/buildCI.yml ================================================ name: build CI on: push: branches: [ '*' ] pull_request: branches: [ master ] workflow_dispatch: jobs: Win_jorb: runs-on: windows-latest strategy: matrix: build_config: ['Debug', 'Release'] arch: ['x64', 'Win32'] defaults: run: shell: cmd env: VULKAN_SDK: ${{ github.workspace }}\VulkanSDK steps: - uses: actions/checkout@v2 with: repository: 'krOoze/vk_sdk_lite' path: ${{ env.VULKAN_SDK }} ref: windows persist-credentials: false - uses: actions/checkout@v2 with: path: 'source' submodules: 'recursive' persist-credentials: false - run: md source\build - run: cmake -G "Visual Studio 16 2019" -A ${{ matrix.arch }} .. working-directory: source\build - run: cmake --build . --config ${{ matrix.build_config }} working-directory: source\build - uses: actions/upload-artifact@v1 with: name: Hello Vulkan Triangle -- Windows ${{ matrix.build_config }} path: source\build\${{ matrix.build_config }}\HelloTriangle.exe linux_jorb: if: endsWith( github.ref, 'dxgi_interop' ) == false runs-on: ubuntu-latest strategy: matrix: build_config: ['Debug', 'Release'] defaults: run: shell: bash env: VULKAN_SDK: ${{ github.workspace }}/VulkanSDK/x86_64 steps: - uses: actions/checkout@v2 with: repository: 'krOoze/vk_sdk_lite' path: 'VulkanSDK' ref: linux persist-credentials: false - run: echo "${{ env.VULKAN_SDK }}/bin" >> $GITHUB_PATH - run: sudo apt update && sudo apt install xorg-dev - uses: actions/checkout@v2 with: path: 'source' submodules: 'recursive' persist-credentials: false - run: mkdir -p source/build - run: cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_config }} -G "Unix Makefiles" .. working-directory: source/build - run: cmake --build . working-directory: source/build - uses: actions/upload-artifact@v1 with: name: Hello Vulkan Triangle App -- Linux ${{ matrix.build_config }} path: source/build/HelloTriangle mac_jorb: if: endsWith( github.ref, 'dxgi_interop' ) == false runs-on: macos-latest strategy: matrix: build_config: ['Debug', 'Release'] defaults: run: shell: bash env: VULKAN_SDK: ${{ github.workspace }}/VulkanSDK/macOS steps: - uses: actions/checkout@v2 with: repository: 'krOoze/vk_sdk_lite' path: 'VulkanSDK' ref: macos persist-credentials: false - run: echo "${{ env.VULKAN_SDK }}/bin" >> $GITHUB_PATH - uses: actions/checkout@v2 with: path: 'source' submodules: 'recursive' persist-credentials: false - run: mkdir -p source/build - run: cmake -DCMAKE_BUILD_TYPE=${{ matrix.build_config }} .. working-directory: source/build - run: cmake --build . working-directory: source/build - uses: actions/upload-artifact@v1 with: name: Hello Vulkan Triangle App -- macOS ${{ matrix.build_config }} path: source/build/HelloTriangle ================================================ FILE: .gitignore ================================================ #CMake /CMakeCache.txt /CMakeFiles/ /cmake_install.cmake #Visual Studio /.vs/ /*.sln /*.vcxproj /*.vcxproj.filters /*.vcxproj.user /HelloTriangle.dir/ /Debug/ /Release/ /Win32/ /x64/ #linux /Makefile #autogenerated shaders **/*.spv.inl #output /*.exe /HelloTriangle /build/ /build32/ /build64/ /out/ ================================================ FILE: .gitmodules ================================================ [submodule "glfw"] path = external/glfw url = https://github.com/glfw/glfw.git ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required( VERSION 3.5.1 ) project( HelloTriangle ) set( TODO ON CACHE BOOL "Enable compiletime TODO messages" ) # Select WSI platform (can use cmake -D) set( WSI "USE_PLATFORM_GLFW" CACHE STRING "WSI type used by this app" ) message( "WSI: " ${WSI} ) # Find Vulkan SDK if( NOT DEFINED {VULKAN_SDK} ) if( NOT DEFINED ENV{VULKAN_SDK} ) message( FATAL_ERROR "VULKAN_SDK not found!" ) endif() if( CYGWIN ) execute_process( COMMAND cygpath "$ENV{VULKAN_SDK}" OUTPUT_VARIABLE VULKAN_SDK ) string( STRIP ${VULKAN_SDK} VULKAN_SDK ) else() set( VULKAN_SDK "$ENV{VULKAN_SDK}" ) endif() endif() message( "Vulkan SDK path: " ${VULKAN_SDK} ) # Compile shaders set( GLSL_DEBUG_FLAG $<$:-g> ) set( GLSL_COMPILER ${VULKAN_SDK}/bin/glslc -mfmt=num ${GLSL_DEBUG_FLAG} ) set(VERT_SHADER "${CMAKE_SOURCE_DIR}/src/shaders/hello_triangle.vert") set(VERT_SHADER_INCLUDE ${VERT_SHADER}.spv.inl) add_custom_command( COMMENT "Compiling vertex shader" MAIN_DEPENDENCY ${VERT_SHADER} OUTPUT ${VERT_SHADER_INCLUDE} COMMAND ${GLSL_COMPILER} -o ${VERT_SHADER_INCLUDE} ${VERT_SHADER} #VERBATIM -- TODO breaks empty generator-expression ) set(FRAG_SHADER "${CMAKE_SOURCE_DIR}/src/shaders/hello_triangle.frag") set(FRAG_SHADER_INCLUDE ${FRAG_SHADER}.spv.inl) add_custom_command( COMMENT "Compiling fragment shader" MAIN_DEPENDENCY ${FRAG_SHADER} OUTPUT ${FRAG_SHADER_INCLUDE} COMMAND ${GLSL_COMPILER} -o ${FRAG_SHADER_INCLUDE} ${FRAG_SHADER} #VERBATIM -- TODO breaks empty generator-expression ) add_custom_target( HelloTriangle_shaders COMMENT "Compiling shaders" DEPENDS ${VERT_SHADER_INCLUDE} ${FRAG_SHADER_INCLUDE} ) # Build GLFW if( ${WSI} STREQUAL "USE_PLATFORM_GLFW" ) set( GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE ) set( GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE ) set( GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE ) if( CYGWIN ) # Hack to make GLFW use Win32 WSI instead of X11 # TODO: Might be cleaner to use cross-compiling unset( UNIX ) set( WIN32 1 ) endif() add_subdirectory( external/glfw ) endif() # HelloTriangle binary set_property( DIRECTORY PROPERTY VS_STARTUP_PROJECT HelloTriangle ) file(GLOB SOURCE_HEADERS "src/*.h" "src/WSI/*.h") add_executable( HelloTriangle WIN32 src/HelloTriangle.cpp ${SOURCE_HEADERS} ) add_dependencies( HelloTriangle HelloTriangle_shaders ) if( NOT TODO ) add_definitions( -DNO_TODO ) endif() if( MSVC ) target_compile_definitions( HelloTriangle PRIVATE $<$:_CONSOLE> ) set_target_properties( HelloTriangle PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE" ) endif() if("${CMAKE_VERSION}" VERSION_LESS 3.8.2) set_target_properties( HelloTriangle PROPERTIES CXX_STANDARD 14 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) else() target_compile_features( HelloTriangle PRIVATE cxx_std_14 ) endif() find_path( VULKAN_INCLUDE vulkan/vulkan.h PATHS "${VULKAN_SDK}/Include" "${VULKAN_SDK}/include" NO_DEFAULT_PATH ) message( "Vulkan include dir: " ${VULKAN_INCLUDE} ) include_directories( "${VULKAN_INCLUDE}" "src/" "src/WSI/" ) if( CYGWIN ) set( CMAKE_FIND_LIBRARY_PREFIXES "" ) set( CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".dll" ) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(VULKAN_LIBRARY_DIRS "${VULKAN_SDK}/lib" "${VULKAN_SDK}/Lib") elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) set(VULKAN_LIBRARY_DIRS "${VULKAN_SDK}/Lib32") endif() find_library( VULKAN_LIBRARY NAMES vulkan vulkan-1 PATHS ${VULKAN_LIBRARY_DIRS} NO_DEFAULT_PATH ) message( "Vulkan libs: " ${VULKAN_LIBRARY} ) if( ${WSI} STREQUAL "USE_PLATFORM_GLFW" ) set( WSI_LIBS glfw ) elseif( ${WSI} STREQUAL "VK_USE_PLATFORM_WIN32_KHR" ) set( WSI_LIBS ) add_definitions( -DUNICODE ) elseif( ${WSI} STREQUAL "VK_USE_PLATFORM_XLIB_KHR" ) set( WSI_LIBS X11 ) elseif( ${WSI} STREQUAL "VK_USE_PLATFORM_XCB_KHR" ) set( WSI_LIBS xcb xcb-keysyms ) elseif( ${WSI} STREQUAL "VK_USE_PLATFORM_WAYLAND_KHR" ) set( WSI_LIBS wayland-client xkbcommon ) endif() add_definitions( -D${WSI} ) target_link_libraries( HelloTriangle "${VULKAN_LIBRARY}" "${WSI_LIBS}" ) ================================================ FILE: CONTRIBUTING.md ================================================ Contributions to this project are welcome. Copyright --------- This project is meant as an educational source without any strings attached to the learner (read [LICENSE](LICENSE)). All contributions are made under the same lincense. Issues ------ Catching bugs is important and so your Issue reports are very welcome. - Issues about specific bugs in the source code should contain specification rule quote it violates. - Other kinds of issues should contain info about OS, driver version, used Vulkan SDK and the output from validation layers. - Relevant questions are also welcome as long as the trafic is low. - New feature suggestions are pointless, unless you are willing to implement them yourself Project goals and conventions ------------------------ This project aims to be a starting point in learning Vulkan API. It aims at people that are bad with tutorials. It tries not to insult the readers inteligence; it tries to do things the right/hard way and it tries not to hide things from the reader. Vulkan is a learning staircase; this is above all meant to be the first stair. - Assume the reader has only vague idea about how Vulkan works. Show the actual commands; do not abstract too much. It should be readable even outsite an IDE. - There is a flat 2 level design. The top level shows the stream of actions using Vulkan terminology (strongly hinting at what Vulkan commands will be used). The second level contains the verbose Vulkan stuff (setting `CreateInfo`s and such). - Assume reader has some computer graphics knowledge; show specific Vulkan features, not effects or algorithms. - The code must be valid and reasonably optimal Vulkan code. This requires carefully reading the [Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.0-extensions/html/vkspec.html) itself. - Suboptimal constructs (like `vkDeviceWaitIdle`s, or suboptimally set barriers) should be avoided. - The code must not be platform dependent (unless it shows a specific non-universally supported feature). - For now examples showing additional Vulkan features are kept as a diff (git branch) to the `master` (which represents the simplest viable Vulkan app). This is to allow easy comparison. Pull requests ------------- Pull requests following the conventions above are welcome. Building should be straightforward and is outlined in [README.md](README.md). If you need a new Branch just PR against `master`. It should be changeable later when new branch is created. ================================================ FILE: LICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: README.md ================================================ Hello Triangle Vulkan demo ========================= This is a traditional Hello World style application for the Vulkan API. It renders a RGB shaded equilateral triangle (well, if the resolution is a square). The code is relatively flat and basic, so I think it's good enough for learning. No tutorial or even much comments are provided though (comments do lie anyway :smirk:). Though some things I found noteworthy are in the `doc/` folder. My goal here is to be perfectly conformant to the Vulkan specification. Also I do handle all the Vulkan `VkResult`s (with app termination though). And I try to do things in the efficient way, rather than simplify. But I am only human. :innocent: If you appriciate any of my free work or help (e.g. hosting this), you can send me some sweet sweet money: Donate $ to krOoze Donate € to krOoze Branches ----------------- The git branches demonstrate some basic Vulkan features which can be easily grafted on this basic example (it is nice to see a diff of what exactly needs to be changed to make it work). Their `README.md` should be edited to reflect the changes. | branch | diff link | description | |---|---|---| | [master](https://github.com/krOoze/Hello_Triangle/tree/master)| -- | Your regular Hello Triangle app, and base for the others | [MSAA](https://github.com/krOoze/Hello_Triangle/tree/MSAA) | [diff](https://github.com/krOoze/Hello_Triangle/compare/MSAA) | Antialiasing (i.e. Vulkan's multisample image resolve) | | [queue_transfer](https://github.com/krOoze/Hello_Triangle/tree/queue_transfer) | [diff](https://github.com/krOoze/Hello_Triangle/compare/queue_transfer) | Transfer of `EXCLUSIVE` image between queue families (separate graphics and compute queue family) | | [vertex_offset](https://github.com/krOoze/Hello_Triangle/tree/vertex_offset) | [diff](https://github.com/krOoze/Hello_Triangle/compare/vertex_offset) | Demonstrates how offset in `vkCmdBindVertexBuffers()` works | | [dxgi_interop](https://github.com/krOoze/Hello_Triangle/tree/dxgi_interop) | [diff](https://github.com/krOoze/Hello_Triangle/compare/dxgi_interop) | An experiment that shows how to import swapchain images from DXGI (Direct3D 12 Swapchain) | Requirements ---------------------------- **OS**: Windows or Linux **Language**: C++14 **Build environment**: (latest) [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) (requires `VULKAN_SDK` variable being set) **Build environment[Windows]**: Visual Studio, Cygwin, or MinGW (or IDEs running on top of them) **Build environment[Linux]**: CMake compatible compiler and build system and `libxcb-dev` and `libxcb-keysyms-dev` **Build environment[MacOS]**: CMake compatible compiler and build system **Build Environment[GLFW]**: GLFW 3.2+ (already included as a git submodule), requires `xorg-dev` (or XCB) package on Linux **Build environment[Xlib]**: Requires `xorg-dev` package **Build environment[XCB]**: Requires `libxcb1-dev`, `libxcb-util-dev`, `libxcb-keysyms1-dev`, and `x11proto-dev` packages **Build environment[Wayland]**: Requires `libwayland-dev` and `libxkbcommon-dev` packages **Target Environment**: installed (latest) Vulkan capable drivers (to see anything) **Target Environment**: GLFW(recommended), XCB, Xlib, or Wayland based windowing system On Unix-like environment refer to [SDK docs](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html) on how to set `VULKAN_SDK` variable. Adding `VkSurface` support for other platforms should be straightforward using the provided ones as a template for it. Files ---------------------------------- | file | description | |---|---| | [doc/synchronizationTutorial.md](doc/synchronizationTutorial.md) | Short explanation of Vulkan synchronization in the context of trivial applications | | [doc/Schema.pdf](doc/Schema.pdf) | Bunch of diagrams explaining the app architecture and synchronization in a graphical form | | external/glfw/ | GLFW git submodule | | src/HelloTriangle.cpp | The app souce code, including the `main()` function | | src/CompilerMessages.h | Allows to make compile-time messages shown in the compiler output | | src/EnumerateScheme.h | A scheme to unify usage of most Vulkan `vkEnumerate*` and `vkGet*` commands | | src/ErrorHandling.h | `VkResult` check helpers + `VK_EXT_debug_utils` extension related stuff | | src/ExtensionLoader.h | Functions handling loading of select Vulkan extension commands | | src/LeanWindowsEnvironment.h | Included conditionally by `VulkanEnvironment.h` and includes lean `windows.h` header | | src/Vertex.h | Just simple Vertex definitions | | src/VulkanEnvironment.h | Contains header configuration, such platform-specific as `VK_USE_PLATFORM_*` | | src/VulkanIntrospection.h | Introspection of Vulkan entities; e.g. convert Vulkan enumerants to strings | | src/Wsi.h | Meta-header including one of the platform-specific headers in WSI directory | | src/WSI/Glfw.h | WSI platform-dependent stuff via GLFW3 library | | src/WSI/Win32.h | Win32 WSI platform-dependent stuff | | src/WSI/Xcb.h | XCB WSI platform-dependent stuff | | src/WSI/Xlib.h | Xlib WSI platform-dependent stuff | | src/WSI/Wayland.h | Wayland WSI platform-dependent stuff | | src/WSI/private/ | Stuff the WSI headers need; currently just generated Wayland protocols | | src/shaders/hello_triangle.vert | The vertex shader program in GLSL | | src/shaders/hello_triangle.frag | The fragment shader program in GLSL | | .gitignore | Git filter file ignoring most probable outputs messing up the local repo | | .gitmodules | Git submodules file describing the dependency on GLFW | | CMakeLists.txt | CMake makefile | | CONTRIBUTING.md | Extra info about contributing code, ideas, and bug reports | | LICENSE | Copyright licence for this project | | README.md | This file | Config --------------------------------------- You can change the application configuration by simply changing following variables in `HelloTriangle.cpp`. | config variable | purpose | default | |---|---|---| | `appName` | Application name; might show up in title of the window | whatever it is in code | | `debugSeverity` | Which kinds of debug message severites will be shown | `WARNING` \| `ERROR` | | `debugType` | Which kinds of debug message types will be shown | all types | | `useAssistantLayer` | Enable Assistant Layer too when debugging (TODO: does not support new way of enabling) | `false` | | `fpsCounter` | Enable FPS counter via `VK_LAYER_LUNARG_monitor` layer | `true` | | `initialWindowWidth` | The initial width of the rendered window | `800` | | `initialWindowHeight` | The initial height of the rendered window | `800` | | `presentMode` | The presentation mode of Vulkan used in swapchain | `VK_PRESENT_MODE_FIFO_KHR` 1| | `clearColor` | Background color of the rendering | gray (`{0.1f, 0.1f, 0.1f, 1.0f}`) | | `forceSeparatePresentQueue` | By default the app prioritizes single Graphics and Present queue. This will create separate queues for testing purposes. There are virtually no platforms currently that naturally have separate Present queue family. | 1 I preferred `VK_PRESENT_MODE_IMMEDIATE_KHR` before but it tends to make coil whine because of the extreme FPS (which could be unnecessarily dangerous to the PCB in the long term). Build ---------------------------------------------- First just get everything: $ git clone --recurse-submodules https://github.com/krOoze/Hello_Triangle.git In many platforms CMake style build should work just fine: $ cmake -G"Your preferred generator" or even just $ cmake . Then use `make`, or the generated Visual Studio `*.sln`, or whatever it created. There are two cmake options (supplied by `-D`): - `WSI` -- set this to `USE_PLATFORM_GLFW` or any `VK_USE_PLATFORM_*_KHR` to select the WSI to be used. Default is GLFW. - `TODO` -- set this to `OFF` to remove TODO messages during compilation. You also might want to add `-DCMAKE_BUILD_TYPE=Debug`. Manual Build ---------------------------------------------- The code base is quite simple and it can be built manually. It only assumes that the `src` folder is in the include path, that you link the GLFW dependency, and that you compile the GLSL shader files into SPIR-V. In Visual Studio you can simply import the code and add the things mentioned above. Either "console" or "window" subsystem is a valid choice on Windows. In Linux distro you would do e.g.: $ g++ --std=c++14 -Wall -m64 -D_DEBUG -DNO_TODO -I$VULKAN_SDK/include -I./src/ -o./HelloTriangle src/HelloTriangle.cpp -ldl -L$VULKAN_SDK/lib -lvulkan -lglfw GLSL shaders can be compiled using `glslc` from LunarG Vulkan SDK like so: On Windows-like environment: %VULKAN_SDK%/Bin/glslc -mfmt=c -o ./src/shaders/hello_triangle.vert.spv.inl ./src/shaders/hello_triangle.vert %VULKAN_SDK%/Bin/glslc -mfmt=c -o ./src/shaders/hello_triangle.frag.spv.inl ./src/shaders/hello_triangle.frag Or on Unix-like environment you would use just `$VULKAN_SDK` instead: $VULKAN_SDK/Bin/glslc -mfmt=c -o ./src/shaders/hello_triangle.vert.spv.inl ./src/shaders/hello_triangle.vert $VULKAN_SDK/Bin/glslc -mfmt=c -o ./src/shaders/hello_triangle.frag.spv.inl ./src/shaders/hello_triangle.frag There are annoying (on purpose) TODOs generated on build. They can be disabled by defining `NO_TODO` preprocessor macro. Using GLFW is optional. You may choose another windowing platform by modifying `VulkanEnvironment.h`, or supplying it via preprocessor. By default, all platforms use GLFW. Run ------------------------ You just run it as you would anything else. Esc does terminate the app. Alt + Enter toggles fullscreen (might not work on some WSI platforms). ================================================ FILE: doc/synchronizationTutorial.md ================================================ up to [README.md](../README.md) Quick Tutorial of basic synchronization of typical renderloop ------------------------------------------------------------------- You will probably have two semaphors: e.g. `imageAcquired` and `renderingDone`. (You would need more `imageAcquired` semaphores to prevent `vkAcquireNextImage` to signal the same semaphore twice, but let's ignore this aspect for now.) Let's cover it in an order in which things should actually get **executed** (i.e. **not** necesserily in the order we called or recorded the commands): 1. You provide `imageAcquired` semaphore to the `vkAcquireNextImageKHR` to be signalled by it. 2. You provide `imageAcquired` semaphore to the command buffer submit to be waited on. As a `pWaitDstStageMask` you provide the stage where your first write access to the swapchain `VkImage` happens. Most often it will be `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` (but could be something different based on your command buffer contents). 3. In the command buffer you change the layout of the swapchain image (Subpasses, Pipeline Barriers, or Events can do that) **from** the present one ( use `VK_IMAGE_LAYOUT_UNDEFINED` as an old layout, which means "sacrifice data" — there's rarely ever need to read it after present ). For source stage choose the exact same one as in step 2 and source access mask should be `0` ( because memory dependency is already part of the previous semaphore wait). For efficient loop, destination stage should also be the same as in step 2 and the new layout and destination access mask should be appropriate for whatever you plan to do (likely `VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL` and `VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT`). In the case this is done with Render Pass (which should be the preferred method when we need to draw something), the first use of the `VkImage` would be the automatic layout transition followed by `loadOp` you provided during creation. For color attachment load operation does occur in the `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage (so providing that value as `dstStageMask` makes sure the layout transition does not clash with the load operation). Access mask depends on the `loadOp` value chosen. The layout transition in render pass (and in Pipeline Barriers too) happens between `srcStageMask` and `dstStageMask` stages. So we do not need to worry about it too much, assuming our barrier\subpass dependency is otherwisely correct. 4. You would write to your `VkImage`. Let's assume that is done with a draw command like `vkCmdDraw` (which implies use of a Render Pass). Now the `loadOp` guarantees that it happens before our first use of an attachment in a render pass. So, no additional synchronization is needed here. 5. In the command buffer you change the layout of the swapchain image (again Subpasses, Pipeline Barriers, or Events can do that) **to** the present one (`VK_IMAGE_LAYOUT_PRESENT_SRC_KHR`). As your source stage, old layout and source access mask choose whatever your last use of the swapchain image was. Destination stage should be `VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT` (i.e. non-blocking, becose that is instead handled by folowing semaphore signal) and the destination access mask should be `0`. In the case of this being a render pass, first a `storeOp` happens after the last use of your image in a render pass. Again that is guaranteed and needs no additional synchronization to order these two. The Store Operation for color attachment happens in `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage and uses `VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT` access. The Store Operation is followed by the automatic layout transition to `finalLayout`. So, to prevent `storeOp` and this transition to clash, `srcStageMask` should be stage the Store Op happens (`VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` as said above). 6. You provide `renderingDone` semaphore to the command buffer submit to be signalled. 7. You provide `renderingDone` semaphore to the present command to be waited on. 8. The circle is now complete! :mask: From this it should be evident we are just declaring dependencies between memory accesses (otherwisely Vulkan is allowed to mercilessly overlap execution of commands). When there is any kind of memory read or write, we must make sure any previous write to the same location has finished. Usually, value used in `dstStageMask` subsequently appears in following `srcStageMask` — so for a simple application it forms a nice dependency chain or path or lifetime of image that is not so hard to reason about: Image Acquire => Semaphore Signal => Semaphore Unsignal in `VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` => `srcStage=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage of Subpass Dependency\Barrier => Layout Transition From Present Layout => `dstStage=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage of Subpass Dependency\Barrier => Load Op (`VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage) => (Implicit Dependency) => Draw (`VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage again\still) => (Implicit Dependency) => Store Op (`VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT` stage again\still) => `srcStage` stage of Subpass Dependency\Barrier => Layout Transition to Present Layout => `dstStage=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT` stage of Subpass Dependency\Barrier => Semaphore Signal => Semaphore Unsignal => Presenting From this diagram I can be sure the synchronization is correct: - The pipeline stage of previous and next step matches - or it is how the given primitive works (e.g. Semaphore wait does cover any previously submitted semaphore signal) - or I am given (relatively rare) specification guarantee that things implicitly happen in specific order (e.g. the Load Op vs first subpass attachment use). ================================================ FILE: src/CompilerMessages.h ================================================ // Allows to show messages during compilation -- somewhat platform dependent #ifndef COMMON_COMPILER_MESSAGES_H #define COMMON_COMPILER_MESSAGES_H // compiler messages -- should work in MSVC and GCC and is ignored by unsupporting compilers #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #ifdef NO_TODO #define TODO(desc) #else #if defined(_MSC_VER) #define TODO(desc) __pragma( message(__FILE__ "(" STRINGIZE(__LINE__) ")" ": warning T: TODO: " desc) ) #elif defined(__GNUG__) #define DO_PRAGMA(x) _Pragma (#x) #define TODO(desc) DO_PRAGMA(message "TODO: " desc) #else #define TODO(desc) #endif #endif // NO_TODO // usage: //TODO("Don't forget to do X.") #endif //COMMON_COMPILER_MESSAGES_H ================================================ FILE: src/EnumerateScheme.h ================================================ // Scheme for the various vkGet* and vkEnumerate* commands // // Following the DRY principle, this implements getting a vector of enumerants // from the similar enumeration commands that return VK_INCOMPELTE. #ifndef COMMON_ENUMERATE_SCHEME_H #define COMMON_ENUMERATE_SCHEME_H #include #include #include #include #include "CompilerMessages.h" #include "ErrorHandling.h" // the enumeration scheme // takes function VkResult cmd( uint32_t count, Element* pArray ) and Vulkan command name (for debugging purposes) // returns vector which contains the enumerants, or throws template< typename Element, typename Cmd > std::vector enumerateScheme( Cmd cmd, const char* cmdName ){ std::vector enumerants; VkResult errorCode; uint32_t enumerantsCount; // repeat until complete array is returned, or error do{ errorCode = cmd( &enumerantsCount, nullptr ); RESULT_HANDLER( errorCode, cmdName ); // get current array size enumerants.resize( enumerantsCount ); errorCode = cmd( &enumerantsCount, enumerants.data() ); // get current array up to enumerantsCount } while( errorCode == VK_INCOMPLETE ); RESULT_HANDLER( errorCode, cmdName ); enumerants.resize( enumerantsCount ); // shrink in case of enumerantsCount1 > enumerantsCount2 enumerants.shrink_to_fit(); // unlikely the vector will grow from this point on anyway return enumerants; } // Adapters for specific Vulkan commands /////////////////////////////////////////////// template< typename Element, typename... Ts, typename = std::enable_if_t::value> > std::vector enumerate( Ts... ); // Tag will be VkInstance if to disambiguate commands that also work on device template< typename Tag, typename Element, typename... Ts, typename = std::enable_if_t::value> > std::vector enumerate( Ts... ); // for vkEnumerateInstanceLayerProperties -- auto v = enumerate(); template<> std::vector enumerate(){ return enumerateScheme( vkEnumerateInstanceLayerProperties, "vkEnumerateInstanceLayerProperties" ); } // for vkEnumerateDeviceLayerProperties -- auto v = enumerate( pd ); template<> std::vector enumerate( VkPhysicalDevice physicalDevice ){ using namespace std::placeholders; const auto cmd = vkEnumerateDeviceLayerProperties; const auto adapterCmd = std::bind( cmd, physicalDevice, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumerateDeviceLayerProperties" ); } // for vkEnumerateInstanceExtensionProperties -- auto v = enumerate( "ln" ); template<> std::vector enumerate( const char* pLayerName ){ using namespace std::placeholders; const auto cmd = vkEnumerateInstanceExtensionProperties; const auto adapterCmd = std::bind( cmd, pLayerName, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumerateInstanceExtensionProperties" ); } // for vkEnumerateInstanceExtensionProperties with nullptr layer -- auto v = enumerate(); template<> std::vector enumerate(){ using namespace std::placeholders; const auto cmd = vkEnumerateInstanceExtensionProperties; const auto adapterCmd = std::bind( cmd, nullptr, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumerateInstanceExtensionProperties" ); } // for vkEnumerateDeviceExtensionProperties -- auto v = enumerate( pd, "ln" ); template<> std::vector enumerate( VkPhysicalDevice physicalDevice, const char* pLayerName ){ using namespace std::placeholders; const auto cmd = vkEnumerateDeviceExtensionProperties; const auto adapterCmd = std::bind( cmd, physicalDevice, pLayerName, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumerateDeviceExtensionProperties" ); } // for vkEnumerateInstanceExtensionProperties with nullptr layer -- auto v = enumerate( pd ); template<> std::vector enumerate( VkPhysicalDevice physicalDevice ){ using namespace std::placeholders; const auto cmd = vkEnumerateDeviceExtensionProperties; const auto adapterCmd = std::bind( cmd, physicalDevice, nullptr, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumerateDeviceExtensionProperties" ); } // for vkEnumeratePhysicalDevices -- auto v = enumerate( i ); template<> std::vector enumerate( VkInstance instance ){ using namespace std::placeholders; const auto cmd = vkEnumeratePhysicalDevices; const auto adapterCmd = std::bind( cmd, instance, _1, _2 ); return enumerateScheme( adapterCmd, "vkEnumeratePhysicalDevices" ); } // for vkGetPhysicalDeviceSurfaceFormatsKHR -- auto v = enumerate( pd, s ); template<> std::vector enumerate( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ){ using namespace std::placeholders; const auto cmd = vkGetPhysicalDeviceSurfaceFormatsKHR; const auto adapterCmd = std::bind( cmd, physicalDevice, surface, _1, _2 ); return enumerateScheme( adapterCmd, "vkGetPhysicalDeviceSurfaceFormatsKHR" ); } // for vkGetPhysicalDeviceSurfacePresentModesKHR -- auto v = enumerate( pd, s ); template<> std::vector enumerate( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ){ using namespace std::placeholders; const auto cmd = vkGetPhysicalDeviceSurfacePresentModesKHR; const auto adapterCmd = std::bind( cmd, physicalDevice, surface, _1, _2 ); return enumerateScheme( adapterCmd, "vkGetPhysicalDeviceSurfacePresentModesKHR" ); } // for vkGetSwapchainImagesKHR -- auto v = enumerate( d, s ); template<> std::vector enumerate( VkDevice device, VkSwapchainKHR swapchain ){ using namespace std::placeholders; const auto cmd = vkGetSwapchainImagesKHR; const auto adapterCmd = std::bind( cmd, device, swapchain, _1, _2 ); return enumerateScheme( adapterCmd, "vkGetSwapchainImagesKHR" ); } // ... others to be added as needed #endif //COMMON_ENUMERATE_SCHEME_H ================================================ FILE: src/ErrorHandling.h ================================================ // Reusable error handling primitives for Vulkan #ifndef COMMON_ERROR_HANDLING_H #define COMMON_ERROR_HANDLING_H #include #include #include #include #include "VulkanIntrospection.h" struct VulkanResultException{ const char* file; unsigned line; const char* func; const char* source; VkResult result; VulkanResultException( const char* file, unsigned line, const char* func, const char* source, VkResult result ) : file( file ), line( line ), func( func ), source( source ), result( result ){} }; #define RESULT_HANDLER( errorCode, source ) if( errorCode ) throw VulkanResultException( __FILE__, __LINE__, __func__, source, errorCode ) #define RESULT_HANDLER_EX( cond, errorCode, source ) if( cond ) throw VulkanResultException( __FILE__, __LINE__, __func__, source, errorCode ) #define RUNTIME_ASSERT( cond, source ) if( !(cond) ) throw source " failed"; // just use cout for logging now std::ostream& logger = std::cout; enum class Highlight{ off, on }; void genericDebugCallback( std::string flags, Highlight highlight, std::string msgCode, std::string object, const char* message ); VKAPI_ATTR VkBool32 VKAPI_CALL genericDebugReportCallback( VkDebugReportFlagsEXT msgFlags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t /*location*/, int32_t msgCode, const char* pLayerPrefix, const char* pMsg, void* /*pUserData*/ ); VKAPI_ATTR VkBool32 VKAPI_CALL genericDebugUtilsCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* /*pUserData*/ ); enum class DebugObjectType{ debugReport, debugUtils } tag; struct DebugObjectVariant{ DebugObjectType tag; union{ VkDebugReportCallbackEXT debugReportCallback; VkDebugUtilsMessengerEXT debugUtilsMessenger; }; }; DebugObjectVariant initDebug( const VkInstance instance, const DebugObjectType debugExtension, const VkDebugUtilsMessageSeverityFlagsEXT debugSeverity, const VkDebugUtilsMessageTypeFlagsEXT debugType ); void killDebug( VkInstance instance, DebugObjectVariant debug ); VkDebugReportFlagsEXT translateFlags( const VkDebugUtilsMessageSeverityFlagsEXT debugSeverity, const VkDebugUtilsMessageTypeFlagsEXT debugType ); // Implementation ////////////////////////////////// void genericDebugCallback( std::string flags, Highlight highlight, std::string msgCode, std::string object, const char* message ){ using std::endl; using std::string; const string report = flags + ": " + object + ": " + msgCode + ", \"" + message + '"'; if( highlight != Highlight::off ){ const string border( 80, '!' ); logger << border << endl; logger << report << endl; logger << border << endl << endl; } else{ logger << report << endl; } } VKAPI_ATTR VkBool32 VKAPI_CALL genericDebugReportCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t /*location*/, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* /*pUserData*/ ){ using std::to_string; using std::string; Highlight highlight; if( (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) || (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) || (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) ){ highlight = Highlight::on; } else highlight = Highlight::off; genericDebugCallback( dbrflags_to_string( flags ), highlight, string(pLayerPrefix) + ", " + to_string( messageCode ), to_string( objectType ) + "(" + to_string_hex( object ) + ")", pMessage ); return VK_FALSE; // no abort on misbehaving command } VKAPI_ATTR VkBool32 VKAPI_CALL genericDebugUtilsCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* /*pUserData*/ ){ using std::to_string; using std::string; Highlight highlight; if( (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) || (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)){ highlight = Highlight::on; } else highlight = Highlight::off; string objects; bool first = true; for( uint32_t i = 0; i < pCallbackData->objectCount; ++i ){ const auto& obj = pCallbackData->pObjects[i]; if( first ) first = false; else objects += ", "; objects += to_string( obj.objectType ) + "(" + to_string_hex( obj.objectHandle ) + ")"; } objects = "[" + objects + "]"; genericDebugCallback( dbutype_to_string( messageTypes ) + "+" + to_string( messageSeverity ), highlight, string(pCallbackData->pMessageIdName) + "(" + to_string( pCallbackData->messageIdNumber ) + ")", objects, pCallbackData->pMessage ); return VK_FALSE; // no abort on misbehaving command } VkDebugReportFlagsEXT translateFlags( const VkDebugUtilsMessageSeverityFlagsEXT debugSeverity, const VkDebugUtilsMessageTypeFlagsEXT debugType ){ VkDebugReportFlagsEXT flags = 0; if( (debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) || (debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) ){ if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT ) flags |= VK_DEBUG_REPORT_ERROR_BIT_EXT; if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ) flags |= VK_DEBUG_REPORT_WARNING_BIT_EXT; if( (debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) && (debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) ) flags |= VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT; if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT ) flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT; if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT ) flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT; } return flags; } DebugObjectVariant initDebug( const VkInstance instance, const DebugObjectType debugExtension, const VkDebugUtilsMessageSeverityFlagsEXT debugSeverity, const VkDebugUtilsMessageTypeFlagsEXT debugType ){ DebugObjectVariant debug; debug.tag = debugExtension; if( debugExtension == DebugObjectType::debugUtils ){ const VkDebugUtilsMessengerCreateInfoEXT dmci = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, nullptr, // pNext 0, // flags debugSeverity, debugType, ::genericDebugUtilsCallback, nullptr // pUserData }; const VkResult errorCode = vkCreateDebugUtilsMessengerEXT( instance, &dmci, nullptr, &debug.debugUtilsMessenger ); RESULT_HANDLER( errorCode, "vkCreateDebugUtilsMessengerEXT" ); } else if( debugExtension == DebugObjectType::debugReport ){ const VkDebugReportCallbackCreateInfoEXT debugCreateInfo{ VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr, // pNext translateFlags( debugSeverity, debugType ), ::genericDebugReportCallback, nullptr // pUserData }; const VkResult errorCode = vkCreateDebugReportCallbackEXT( instance, &debugCreateInfo, nullptr, &debug.debugReportCallback ); RESULT_HANDLER( errorCode, "vkCreateDebugReportCallbackEXT" ); } else{ throw "initDebug: unknown debug extension"; } return debug; } void killDebug( const VkInstance instance, const DebugObjectVariant debug ){ if( debug.tag == DebugObjectType::debugUtils ){ vkDestroyDebugUtilsMessengerEXT( instance, debug.debugUtilsMessenger, nullptr ); } else if( debug.tag == DebugObjectType::debugReport ){ vkDestroyDebugReportCallbackEXT( instance, debug.debugReportCallback, nullptr ); } else{ throw "initDebug: unknown debug extension"; } } #endif //COMMON_ERROR_HANDLING_H ================================================ FILE: src/ExtensionLoader.h ================================================ // Vulkan extensions commands loader #ifndef EXTENSION_LOADER_H #define EXTENSION_LOADER_H #include #include #include #include #include "CompilerMessages.h" #include "EnumerateScheme.h" void loadInstanceExtensionsCommands( VkInstance instance, const std::vector& instanceExtensions ); void unloadInstanceExtensionsCommands( VkInstance instance ); void loadDeviceExtensionsCommands( VkDevice device, const std::vector& instanceExtensions ); void unloadDeviceExtensionsCommands( VkDevice device ); void loadPDProps2Commands( VkInstance instance ); void unloadPDProps2Commands( VkInstance instance ); void loadDebugReportCommands( VkInstance instance ); void unloadDebugReportCommands( VkInstance instance ); void loadDebugUtilsCommands( VkInstance instance ); void unloadDebugUtilsCommands( VkInstance instance ); void loadExternalMemoryCapsCommands( VkInstance instance ); void unloadExternalMemoryCapsCommands( VkInstance instance ); void loadExternalMemoryCommands( VkDevice device ); void unloadExternalMemoryCommands( VkDevice device ); #ifdef VK_USE_PLATFORM_WIN32_KHR void loadExternalMemoryWin32Commands( VkDevice device ); void unloadExternalMemoryWin32Commands( VkDevice device ); #endif void loadDedicatedAllocationCommands( VkDevice device ); void unloadDedicatedAllocationCommands( VkDevice device ); //////////////////////////////////////////////////////// std::unordered_map< VkInstance, std::vector > instanceExtensionsMap; std::unordered_map< VkPhysicalDevice, VkInstance > physicalDeviceInstanceMap; TODO( "Leaks destroyed instances" ); void populatePhysicalDeviceInstaceMap( const VkInstance instance ){ const std::vector physicalDevices = enumerate( instance ); for( const auto pd : physicalDevices ) physicalDeviceInstanceMap[pd] = instance; } void loadInstanceExtensionsCommands( const VkInstance instance, const std::vector& instanceExtensions ){ using std::strcmp; instanceExtensionsMap[instance] = instanceExtensions; for( const auto e : instanceExtensions ){ if( strcmp( e, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME ) == 0 ) loadPDProps2Commands( instance ); if( strcmp( e, VK_EXT_DEBUG_REPORT_EXTENSION_NAME ) == 0 ) loadDebugReportCommands( instance ); if( strcmp( e, VK_EXT_DEBUG_UTILS_EXTENSION_NAME ) == 0 ) loadDebugUtilsCommands( instance ); if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME ) == 0 ) loadExternalMemoryCapsCommands( instance ); // ... } } void unloadInstanceExtensionsCommands( const VkInstance instance ){ using std::strcmp; for( const auto e : instanceExtensionsMap.at( instance ) ){ if( strcmp( e, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME ) == 0 ) unloadPDProps2Commands( instance ); if( strcmp( e, VK_EXT_DEBUG_REPORT_EXTENSION_NAME ) == 0 ) unloadDebugReportCommands( instance ); if( strcmp( e, VK_EXT_DEBUG_UTILS_EXTENSION_NAME ) == 0 ) unloadDebugUtilsCommands( instance ); if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME ) == 0 ) unloadExternalMemoryCapsCommands( instance ); // ... } instanceExtensionsMap.erase( instance ); } std::unordered_map< VkDevice, std::vector > deviceExtensionsMap; void loadDeviceExtensionsCommands( const VkDevice device, const std::vector& deviceExtensions ){ using std::strcmp; deviceExtensionsMap[device] = deviceExtensions; for( const auto e : deviceExtensions ){ if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME ) == 0 ) loadExternalMemoryCommands( device ); #ifdef VK_USE_PLATFORM_WIN32_KHR if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME ) == 0 ) loadExternalMemoryWin32Commands( device ); #endif if( strcmp( e, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME ) == 0 ) loadDedicatedAllocationCommands( device ); // ... } } void unloadDeviceExtensionsCommands( const VkDevice device ){ using std::strcmp; for( const auto e : deviceExtensionsMap.at( device ) ){ if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME ) == 0 ) unloadExternalMemoryCommands( device ); #ifdef VK_USE_PLATFORM_WIN32_KHR if( strcmp( e, VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME ) == 0 ) unloadExternalMemoryWin32Commands( device ); #endif if( strcmp( e, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME ) == 0 ) unloadDedicatedAllocationCommands( device ); // ... } deviceExtensionsMap.erase( device ); } // VK_KHR_get_physical_device_properties2 /////////////////////////////////////////// std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceFeatures2KHR > GetPhysicalDeviceFeatures2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceProperties2KHR > GetPhysicalDeviceProperties2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceFormatProperties2KHR > GetPhysicalDeviceFormatProperties2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceImageFormatProperties2KHR > GetPhysicalDeviceImageFormatProperties2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceQueueFamilyProperties2KHR > GetPhysicalDeviceQueueFamilyProperties2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceMemoryProperties2KHR > GetPhysicalDeviceMemoryProperties2KHRDispatchTable; std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceSparseImageFormatProperties2KHR > GetPhysicalDeviceSparseImageFormatProperties2KHRDispatchTable; void loadPDProps2Commands( VkInstance instance ){ populatePhysicalDeviceInstaceMap( instance ); PFN_vkVoidFunction temp_fp; temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceFeatures2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceFeatures2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceFeatures2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceFormatProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceFormatProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceFormatProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceImageFormatProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceImageFormatProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceImageFormatProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceQueueFamilyProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceQueueFamilyProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceQueueFamilyProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceMemoryProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceMemoryProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceMemoryProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceSparseImageFormatProperties2KHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceSparseImageFormatProperties2KHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceSparseImageFormatProperties2KHRDispatchTable[instance] = reinterpret_cast( temp_fp ); } void unloadPDProps2Commands( VkInstance instance ){ GetPhysicalDeviceFeatures2KHRDispatchTable.erase( instance ); GetPhysicalDeviceProperties2KHRDispatchTable.erase( instance ); GetPhysicalDeviceFormatProperties2KHRDispatchTable.erase( instance ); GetPhysicalDeviceImageFormatProperties2KHRDispatchTable.erase( instance ); GetPhysicalDeviceQueueFamilyProperties2KHRDispatchTable.erase( instance ); GetPhysicalDeviceMemoryProperties2KHRDispatchTable.erase( instance ); GetPhysicalDeviceSparseImageFormatProperties2KHRDispatchTable.erase( instance ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFeatures2KHR( VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures2* pFeatures ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceFeatures2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pFeatures ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceProperties2KHR( VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2* pProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pProperties ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceFormatProperties2KHR( VkPhysicalDevice physicalDevice, VkFormat format, VkFormatProperties2* pFormatProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceFormatProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, format, pFormatProperties ); } VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceImageFormatProperties2KHR( VkPhysicalDevice physicalDevice, const VkPhysicalDeviceImageFormatInfo2* pImageFormatInfo, VkImageFormatProperties2* pImageFormatProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceImageFormatProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pImageFormatInfo, pImageFormatProperties ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties2KHR( VkPhysicalDevice physicalDevice, uint32_t* pQueueFamilyPropertyCount, VkQueueFamilyProperties2* pQueueFamilyProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceQueueFamilyProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pQueueFamilyPropertyCount, pQueueFamilyProperties ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceMemoryProperties2KHR( VkPhysicalDevice physicalDevice, VkPhysicalDeviceMemoryProperties2* pMemoryProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceMemoryProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pMemoryProperties ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceSparseImageFormatProperties2KHR( VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSparseImageFormatInfo2* pFormatInfo, uint32_t* pPropertyCount, VkSparseImageFormatProperties2* pProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceSparseImageFormatProperties2KHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pFormatInfo, pPropertyCount, pProperties ); } // VK_EXT_debug_report ////////////////////////////////// std::unordered_map< VkInstance, PFN_vkCreateDebugReportCallbackEXT > CreateDebugReportCallbackEXTDispatchTable; std::unordered_map< VkInstance, PFN_vkDestroyDebugReportCallbackEXT > DestroyDebugReportCallbackEXTDispatchTable; std::unordered_map< VkInstance, PFN_vkDebugReportMessageEXT > DebugReportMessageEXTDispatchTable; void loadDebugReportCommands( VkInstance instance ){ PFN_vkVoidFunction temp_fp; temp_fp = vkGetInstanceProcAddr( instance, "vkCreateDebugReportCallbackEXT" ); if( !temp_fp ) throw "Failed to load vkCreateDebugReportCallbackEXT"; // check shouldn't be necessary (based on spec) CreateDebugReportCallbackEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkDestroyDebugReportCallbackEXT" ); if( !temp_fp ) throw "Failed to load vkDestroyDebugReportCallbackEXT"; // check shouldn't be necessary (based on spec) DestroyDebugReportCallbackEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkDebugReportMessageEXT" ); if( !temp_fp ) throw "Failed to load vkDebugReportMessageEXT"; // check shouldn't be necessary (based on spec) DebugReportMessageEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); } void unloadDebugReportCommands( VkInstance instance ){ CreateDebugReportCallbackEXTDispatchTable.erase( instance ); DestroyDebugReportCallbackEXTDispatchTable.erase( instance ); DebugReportMessageEXTDispatchTable.erase( instance ); } VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugReportCallbackEXT( VkInstance instance, const VkDebugReportCallbackCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugReportCallbackEXT* pCallback ){ auto dispatched_cmd = CreateDebugReportCallbackEXTDispatchTable.at( instance ); return dispatched_cmd( instance, pCreateInfo, pAllocator, pCallback ); } VKAPI_ATTR void VKAPI_CALL vkDestroyDebugReportCallbackEXT( VkInstance instance, VkDebugReportCallbackEXT callback, const VkAllocationCallbacks* pAllocator ){ auto dispatched_cmd = DestroyDebugReportCallbackEXTDispatchTable.at( instance ); return dispatched_cmd( instance, callback, pAllocator ); } VKAPI_ATTR void VKAPI_CALL vkDebugReportMessageEXT( VkInstance instance, VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage ){ auto dispatched_cmd = DebugReportMessageEXTDispatchTable.at( instance ); return dispatched_cmd( instance, flags, objectType, object, location, messageCode, pLayerPrefix, pMessage ); } // VK_EXT_debug_utils ////////////////////////////////// std::unordered_map< VkInstance, PFN_vkCreateDebugUtilsMessengerEXT > CreateDebugUtilsMessengerEXTDispatchTable; std::unordered_map< VkInstance, PFN_vkDestroyDebugUtilsMessengerEXT > DestroyDebugUtilsMessengerEXTDispatchTable; std::unordered_map< VkInstance, PFN_vkSubmitDebugUtilsMessageEXT > SubmitDebugUtilsMessageEXTDispatchTable; void loadDebugUtilsCommands( VkInstance instance ){ PFN_vkVoidFunction temp_fp; temp_fp = vkGetInstanceProcAddr( instance, "vkCreateDebugUtilsMessengerEXT" ); if( !temp_fp ) throw "Failed to load vkCreateDebugUtilsMessengerEXT"; // check shouldn't be necessary (based on spec) CreateDebugUtilsMessengerEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkDestroyDebugUtilsMessengerEXT" ); if( !temp_fp ) throw "Failed to load vkDestroyDebugUtilsMessengerEXT"; // check shouldn't be necessary (based on spec) DestroyDebugUtilsMessengerEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); temp_fp = vkGetInstanceProcAddr( instance, "vkSubmitDebugUtilsMessageEXT" ); if( !temp_fp ) throw "Failed to load vkSubmitDebugUtilsMessageEXT"; // check shouldn't be necessary (based on spec) SubmitDebugUtilsMessageEXTDispatchTable[instance] = reinterpret_cast( temp_fp ); } void unloadDebugUtilsCommands( VkInstance instance ){ CreateDebugUtilsMessengerEXTDispatchTable.erase( instance ); DestroyDebugUtilsMessengerEXTDispatchTable.erase( instance ); SubmitDebugUtilsMessageEXTDispatchTable.erase( instance ); } VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT( VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pMessenger ){ auto dispatched_cmd = CreateDebugUtilsMessengerEXTDispatchTable.at( instance ); return dispatched_cmd( instance, pCreateInfo, pAllocator, pMessenger ); } VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT( VkInstance instance, VkDebugUtilsMessengerEXT messenger, const VkAllocationCallbacks* pAllocator ){ auto dispatched_cmd = DestroyDebugUtilsMessengerEXTDispatchTable.at( instance ); return dispatched_cmd( instance, messenger, pAllocator ); } VKAPI_ATTR void VKAPI_CALL vkSubmitDebugUtilsMessageEXT( VkInstance instance, VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData ){ auto dispatched_cmd = SubmitDebugUtilsMessageEXTDispatchTable.at( instance ); return dispatched_cmd( instance, messageSeverity, messageTypes, pCallbackData ); } // VK_KHR_external_memory_capabilities /////////////////////////////////////////// std::unordered_map< VkInstance, PFN_vkGetPhysicalDeviceExternalBufferPropertiesKHR > GetPhysicalDeviceExternalBufferPropertiesKHRDispatchTable; void loadExternalMemoryCapsCommands( VkInstance instance ){ populatePhysicalDeviceInstaceMap( instance ); PFN_vkVoidFunction temp_fp; temp_fp = vkGetInstanceProcAddr( instance, "vkGetPhysicalDeviceExternalBufferPropertiesKHR" ); if( !temp_fp ) throw "Failed to load vkGetPhysicalDeviceExternalBufferPropertiesKHR"; // check shouldn't be necessary (based on spec) GetPhysicalDeviceExternalBufferPropertiesKHRDispatchTable[instance] = reinterpret_cast( temp_fp ); } void unloadExternalMemoryCapsCommands( VkInstance instance ){ GetPhysicalDeviceExternalBufferPropertiesKHRDispatchTable.erase( instance ); } VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceExternalBufferPropertiesKHR( VkPhysicalDevice physicalDevice, const VkPhysicalDeviceExternalBufferInfo* pExternalBufferInfo, VkExternalBufferProperties* pExternalBufferProperties ){ const VkInstance instance = physicalDeviceInstanceMap.at( physicalDevice ); auto dispatched_cmd = GetPhysicalDeviceExternalBufferPropertiesKHRDispatchTable.at( instance ); return dispatched_cmd( physicalDevice, pExternalBufferInfo, pExternalBufferProperties ); } // VK_KHR_external_memory /////////////////////////////////////////// void loadExternalMemoryCommands( VkDevice ){ // no commands } void unloadExternalMemoryCommands( VkDevice ){ // no commands } #ifdef VK_USE_PLATFORM_WIN32_KHR // VK_KHR_external_memory_win32 /////////////////////////////////////////// std::unordered_map< VkDevice, PFN_vkGetMemoryWin32HandleKHR > GetMemoryWin32HandleKHRDispatchTable; std::unordered_map< VkDevice, PFN_vkGetMemoryWin32HandlePropertiesKHR > GetMemoryWin32HandlePropertiesKHRDispatchTable; void loadExternalMemoryWin32Commands( VkDevice device ){ PFN_vkVoidFunction temp_fp; temp_fp = vkGetDeviceProcAddr( device, "vkGetMemoryWin32HandleKHR" ); if( !temp_fp ) throw "Failed to load vkGetMemoryWin32HandleKHR"; // check shouldn't be necessary (based on spec) GetMemoryWin32HandleKHRDispatchTable[device] = reinterpret_cast( temp_fp ); temp_fp = vkGetDeviceProcAddr( device, "vkGetMemoryWin32HandlePropertiesKHR" ); if( !temp_fp ) throw "Failed to load vkGetMemoryWin32HandlePropertiesKHR"; // check shouldn't be necessary (based on spec) GetMemoryWin32HandlePropertiesKHRDispatchTable[device] = reinterpret_cast( temp_fp ); } void unloadExternalMemoryWin32Commands( VkDevice device ){ GetMemoryWin32HandleKHRDispatchTable.erase( device ); GetMemoryWin32HandlePropertiesKHRDispatchTable.erase( device ); } VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandleKHR( VkDevice device, const VkMemoryGetWin32HandleInfoKHR* pGetWin32HandleInfo, HANDLE* pHandle ){ auto dispatched_cmd = GetMemoryWin32HandleKHRDispatchTable.at( device ); return dispatched_cmd( device, pGetWin32HandleInfo, pHandle ); } VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryWin32HandlePropertiesKHR( VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, HANDLE handle, VkMemoryWin32HandlePropertiesKHR* pMemoryWin32HandleProperties ){ auto dispatched_cmd = GetMemoryWin32HandlePropertiesKHRDispatchTable.at( device ); return dispatched_cmd( device, handleType, handle, pMemoryWin32HandleProperties ); } #endif // VK_KHR_dedicated_allocation /////////////////////////////////////////// void loadDedicatedAllocationCommands( VkDevice ){ // no commands } void unloadDedicatedAllocationCommands( VkDevice ){ // no commands } #endif //EXTENSION_LOADER_H ================================================ FILE: src/HelloTriangle.cpp ================================================ // Vulkan hello world triangle rendering demo // Global header settings ////////////////////////////////////////////////////////////////////////////////// #include "VulkanEnvironment.h" // first include must be before vulkan.h and platform header // Includes ////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // also assume core+WSI commands are loaded static_assert( VK_HEADER_VERSION >= REQUIRED_HEADER_VERSION, "Update your SDK! This app is written against Vulkan header version " STRINGIZE(REQUIRED_HEADER_VERSION) "." ); #include "EnumerateScheme.h" #include "ErrorHandling.h" #include "ExtensionLoader.h" #include "Vertex.h" #include "Wsi.h" using std::exception; using std::runtime_error; using std::string; using std::to_string; using std::vector; // Config ////////////////////////////////////////////////////////////////////////////////// const char appName[] = u8"Hello Vulkan Triangle"; // layers and debug #if VULKAN_VALIDATION constexpr VkDebugUtilsMessageSeverityFlagsEXT debugSeverity = 0 //| VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT //| VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT ; constexpr VkDebugUtilsMessageTypeFlagsEXT debugType = 0 | VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT ; constexpr bool useAssistantLayer = false; #endif constexpr bool fpsCounter = true; // window and swapchain constexpr uint32_t initialWindowWidth = 800; constexpr uint32_t initialWindowHeight = 800; //constexpr VkPresentModeKHR presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; // better not be used often because of coil whine constexpr VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR; //constexpr VkPresentModeKHR presentMode = VK_PRESENT_MODE_MAILBOX_KHR; // pipeline settings constexpr VkClearValue clearColor = { { {0.1f, 0.1f, 0.1f, 1.0f} } }; // Makes present queue from different Queue Family than Graphics, for testing purposes constexpr bool forceSeparatePresentQueue = false; // needed stuff for main() -- forward declarations ////////////////////////////////////////////////////////////////////////////////// bool isLayerSupported( const char* layer, const vector& supportedLayers ); bool isExtensionSupported( const char* extension, const vector& supportedExtensions ); // treat layers as optional; app can always run without em -- i.e. return those supported vector checkInstanceLayerSupport( const vector& requestedLayers, const vector& supportedLayers ); vector getSupportedInstanceExtensions( const vector& providingLayers ); bool checkExtensionSupport( const vector& extensions, const vector& supportedExtensions ); VkInstance initInstance( const vector& layers = {}, const vector& extensions = {} ); void killInstance( VkInstance instance ); VkPhysicalDevice getPhysicalDevice( VkInstance instance, VkSurfaceKHR surface = VK_NULL_HANDLE /*seek presentation support if !NULL*/ ); // destroyed with instance VkPhysicalDeviceProperties getPhysicalDeviceProperties( VkPhysicalDevice physicalDevice ); VkPhysicalDeviceMemoryProperties getPhysicalDeviceMemoryProperties( VkPhysicalDevice physicalDevice ); std::pair getQueueFamilies( VkPhysicalDevice physDevice, VkSurfaceKHR surface ); vector getQueueFamilyProperties( VkPhysicalDevice device ); VkDevice initDevice( VkPhysicalDevice physDevice, const VkPhysicalDeviceFeatures& features, uint32_t graphicsQueueFamily, uint32_t presentQueueFamily, const vector& layers = {}, const vector& extensions = {} ); void killDevice( VkDevice device ); VkQueue getQueue( VkDevice device, uint32_t queueFamily, uint32_t queueIndex ); enum class ResourceType{ Buffer, Image }; template< ResourceType resourceType, class T > VkDeviceMemory initMemory( VkDevice device, VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties, T resource, const std::vector& memoryTypePriority ); void setMemoryData( VkDevice device, VkDeviceMemory memory, void* begin, size_t size ); void killMemory( VkDevice device, VkDeviceMemory memory ); VkBuffer initBuffer( VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage ); void killBuffer( VkDevice device, VkBuffer buffer ); VkImage initImage( VkDevice device, VkFormat format, uint32_t width, uint32_t height, VkSampleCountFlagBits samples, VkImageUsageFlags usage ); void killImage( VkDevice device, VkImage image ); VkImageView initImageView( VkDevice device, VkImage image, VkFormat format ); void killImageView( VkDevice device, VkImageView imageView ); // initSurface() is platform dependent void killSurface( VkInstance instance, VkSurfaceKHR surface ); VkSurfaceCapabilitiesKHR getSurfaceCapabilities( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ); VkSurfaceFormatKHR getSurfaceFormat( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ); VkSwapchainKHR initSwapchain( VkPhysicalDevice physicalDevice, VkDevice device, VkSurfaceKHR surface, VkSurfaceFormatKHR surfaceFormat, VkSurfaceCapabilitiesKHR capabilities, uint32_t graphicsQueueFamily, uint32_t presentQueueFamily, VkSwapchainKHR oldSwapchain = VK_NULL_HANDLE ); void killSwapchain( VkDevice device, VkSwapchainKHR swapchain ); uint32_t getNextImageIndex( VkDevice device, VkSwapchainKHR swapchain, VkSemaphore imageReadyS ); vector initSwapchainImageViews( VkDevice device, vector images, VkFormat format ); void killSwapchainImageViews( VkDevice device, vector& imageViews ); VkRenderPass initRenderPass( VkDevice device, VkSurfaceFormatKHR surfaceFormat ); void killRenderPass( VkDevice device, VkRenderPass renderPass ); vector initFramebuffers( VkDevice device, VkRenderPass renderPass, vector imageViews, uint32_t width, uint32_t height ); void killFramebuffers( VkDevice device, vector& framebuffers ); VkShaderModule initShaderModule( VkDevice device, const vector& shaderCode ); VkShaderModule initShaderModule( VkDevice device, string filename ); void killShaderModule( VkDevice device, VkShaderModule shaderModule ); VkPipelineLayout initPipelineLayout( VkDevice device ); void killPipelineLayout( VkDevice device, VkPipelineLayout pipelineLayout ); VkPipeline initPipeline( VkDevice device, VkPhysicalDeviceLimits limits, VkPipelineLayout pipelineLayout, VkRenderPass renderPass, VkShaderModule vertexShader, VkShaderModule fragmentShader, const uint32_t vertexBufferBinding, uint32_t width, uint32_t height ); void killPipeline( VkDevice device, VkPipeline pipeline ); void setVertexData( VkDevice device, VkDeviceMemory memory, vector vertices ); VkSemaphore initSemaphore( VkDevice device ); vector initSemaphores( VkDevice device, size_t count ); void killSemaphore( VkDevice device, VkSemaphore semaphore ); void killSemaphores( VkDevice device, vector& semaphores ); VkCommandPool initCommandPool( VkDevice device, const uint32_t queueFamily ); void killCommandPool( VkDevice device, VkCommandPool commandPool ); vector initFences( VkDevice device, size_t count, VkFenceCreateFlags flags = 0 ); void killFences( VkDevice device, vector& fences ); void acquireCommandBuffers( VkDevice device, VkCommandPool commandPool, uint32_t count, vector& commandBuffers ); void beginCommandBuffer( VkCommandBuffer commandBuffer ); void endCommandBuffer( VkCommandBuffer commandBuffer ); void recordBeginRenderPass( VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer, VkClearValue clearValue, uint32_t width, uint32_t height ); void recordEndRenderPass( VkCommandBuffer commandBuffer ); void recordBindPipeline( VkCommandBuffer commandBuffer, VkPipeline pipeline ); void recordBindVertexBuffer( VkCommandBuffer commandBuffer, const uint32_t vertexBufferBinding, VkBuffer vertexBuffer ); void recordDraw( VkCommandBuffer commandBuffer, uint32_t vertexCount ); void submitToQueue( VkQueue queue, VkCommandBuffer commandBuffer, VkSemaphore imageReadyS, VkSemaphore renderDoneS, VkFence fence = VK_NULL_HANDLE ); void present( VkQueue queue, VkSwapchainKHR swapchain, uint32_t swapchainImageIndex, VkSemaphore renderDoneS ); // cleanup dangerous semaphore with signal pending from vkAcquireNextImageKHR void cleanupUnsafeSemaphore( VkQueue queue, VkSemaphore semaphore ); // main()! ////////////////////////////////////////////////////////////////////////////////// int helloTriangle() try{ const uint32_t vertexBufferBinding = 0; const float triangleSize = 1.6f; const vector triangle = { { /*rb*/ { { 0.5f * triangleSize, sqrtf( 3.0f ) * 0.25f * triangleSize} }, /*R*/{ {1.0f, 0.0f, 0.0f} } }, { /* t*/ { { 0.0f, -sqrtf( 3.0f ) * 0.25f * triangleSize} }, /*G*/{ {0.0f, 1.0f, 0.0f} } }, { /*lb*/ { {-0.5f * triangleSize, sqrtf( 3.0f ) * 0.25f * triangleSize} }, /*B*/{ {0.0f, 0.0f, 1.0f} } } }; const auto supportedLayers = enumerate(); vector requestedLayers; #if VULKAN_VALIDATION if( isLayerSupported( "VK_LAYER_KHRONOS_validation", supportedLayers ) ) requestedLayers.push_back( "VK_LAYER_KHRONOS_validation" ); else throw "VULKAN_VALIDATION is enabled but VK_LAYER_KHRONOS_validation layers are not supported!"; if( ::useAssistantLayer ){ if( isLayerSupported( "VK_LAYER_LUNARG_assistant_layer", supportedLayers ) ) requestedLayers.push_back( "VK_LAYER_LUNARG_assistant_layer" ); else throw "VULKAN_VALIDATION is enabled but VK_LAYER_LUNARG_assistant_layer layer is not supported!"; } #endif if( ::fpsCounter ) requestedLayers.push_back( "VK_LAYER_LUNARG_monitor" ); requestedLayers = checkInstanceLayerSupport( requestedLayers, supportedLayers ); const auto supportedInstanceExtensions = getSupportedInstanceExtensions( requestedLayers ); const auto platformSurfaceExtension = getPlatformSurfaceExtensionName(); vector requestedInstanceExtensions = { VK_KHR_SURFACE_EXTENSION_NAME, platformSurfaceExtension.c_str() }; #if VULKAN_VALIDATION DebugObjectType debugExtensionTag; if( isExtensionSupported( VK_EXT_DEBUG_UTILS_EXTENSION_NAME, supportedInstanceExtensions ) ){ debugExtensionTag = DebugObjectType::debugUtils; requestedInstanceExtensions.push_back( VK_EXT_DEBUG_UTILS_EXTENSION_NAME ); } else if( isExtensionSupported( VK_EXT_DEBUG_REPORT_EXTENSION_NAME, supportedInstanceExtensions ) ){ debugExtensionTag = DebugObjectType::debugReport; requestedInstanceExtensions.push_back( VK_EXT_DEBUG_REPORT_EXTENSION_NAME ); } else throw "VULKAN_VALIDATION is enabled but neither VK_EXT_debug_utils nor VK_EXT_debug_report extension is supported!"; #endif checkExtensionSupport( requestedInstanceExtensions, supportedInstanceExtensions ); const VkInstance instance = initInstance( requestedLayers, requestedInstanceExtensions ); #if VULKAN_VALIDATION const auto debugHandle = initDebug( instance, debugExtensionTag, ::debugSeverity, ::debugType ); const int32_t uncoded = 0; const char* introMsg = "Validation Layers are enabled!"; if( debugExtensionTag == DebugObjectType::debugUtils ){ VkDebugUtilsObjectNameInfoEXT object = { VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, nullptr, // pNext VK_OBJECT_TYPE_INSTANCE, handleToUint64(instance), "instance" }; const VkDebugUtilsMessengerCallbackDataEXT dumcd = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CALLBACK_DATA_EXT, nullptr, // pNext 0, // flags "VULKAN_VALIDATION", // VUID 0, // VUID hash introMsg, 0, nullptr, 0, nullptr, 1, &object }; vkSubmitDebugUtilsMessageEXT( instance, VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, &dumcd ); } else if( debugExtensionTag == DebugObjectType::debugReport ){ vkDebugReportMessageEXT( instance, VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT, (uint64_t)instance, __LINE__, uncoded, "Application", introMsg ); } #endif const PlatformWindow window = initWindow( ::appName, ::initialWindowWidth, ::initialWindowHeight ); const VkSurfaceKHR surface = initSurface( instance, window ); const VkPhysicalDevice physicalDevice = getPhysicalDevice( instance, surface ); const VkPhysicalDeviceProperties physicalDeviceProperties = getPhysicalDeviceProperties( physicalDevice ); const VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties = getPhysicalDeviceMemoryProperties( physicalDevice ); uint32_t graphicsQueueFamily, presentQueueFamily; std::tie( graphicsQueueFamily, presentQueueFamily ) = getQueueFamilies( physicalDevice, surface ); const VkPhysicalDeviceFeatures features = {}; // don't need any special feature for this demo const vector deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; const VkDevice device = initDevice( physicalDevice, features, graphicsQueueFamily, presentQueueFamily, requestedLayers, deviceExtensions ); const VkQueue graphicsQueue = getQueue( device, graphicsQueueFamily, 0 ); const VkQueue presentQueue = getQueue( device, presentQueueFamily, 0 ); VkSurfaceFormatKHR surfaceFormat = getSurfaceFormat( physicalDevice, surface ); VkRenderPass renderPass = initRenderPass( device, surfaceFormat ); vector vertexShaderBinary = { #include "shaders/hello_triangle.vert.spv.inl" }; vector fragmentShaderBinary = { #include "shaders/hello_triangle.frag.spv.inl" }; VkShaderModule vertexShader = initShaderModule( device, vertexShaderBinary ); VkShaderModule fragmentShader = initShaderModule( device, fragmentShaderBinary ); VkPipelineLayout pipelineLayout = initPipelineLayout( device ); VkBuffer vertexBuffer = initBuffer( device, sizeof( decltype( triangle )::value_type ) * triangle.size(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT ); const std::vector memoryTypePriority{ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, // preferably wanna device-side memory that can be updated from host without hassle VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT // guaranteed to allways be supported }; VkDeviceMemory vertexBufferMemory = initMemory( device, physicalDeviceMemoryProperties, vertexBuffer, memoryTypePriority ); setVertexData( device, vertexBufferMemory, triangle ); // Writes throug memory map. Synchronization is implicit for any subsequent vkQueueSubmit batches. VkCommandPool commandPool = initCommandPool( device, graphicsQueueFamily ); // might need synchronization if init is more advanced than this //VkResult errorCode = vkDeviceWaitIdle( device ); RESULT_HANDLER( errorCode, "vkDeviceWaitIdle" ); // place-holder swapchain dependent objects VkSwapchainKHR swapchain = VK_NULL_HANDLE; // has to be NULL -- signifies that there's no swapchain vector swapchainImageViews; vector framebuffers; VkPipeline pipeline = VK_NULL_HANDLE; // has to be NULL for the case the app ends before even first swapchain vector commandBuffers; vector imageReadySs; vector renderDoneSs; // workaround for validation layer "memory leak" + might also help the driver to cleanup old resources // this should not be needed for a real-word app, because they are likely to use fences naturaly (e.g. responding to user input ) // read https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/1628 const uint32_t maxInflightSubmissions = 2; // more than 2 probably does not make much sense uint32_t submissionNr = 0; // index of the current submission modulo maxInflightSubmission vector submissionFences; const std::function recreateSwapchain = [&](){ // swapchain recreation -- will be done before the first frame too; TODO( "This may be triggered from many sources (e.g. WM_SIZE event, and VK_ERROR_OUT_OF_DATE_KHR too). Should prevent duplicate swapchain recreation." ) const VkSwapchainKHR oldSwapchain = swapchain; swapchain = VK_NULL_HANDLE; VkSurfaceCapabilitiesKHR capabilities = getSurfaceCapabilities( physicalDevice, surface ); if( capabilities.currentExtent.width == UINT32_MAX && capabilities.currentExtent.height == UINT32_MAX ){ capabilities.currentExtent.width = getWindowWidth( window ); capabilities.currentExtent.height = getWindowHeight( window ); } VkExtent2D surfaceSize = { capabilities.currentExtent.width, capabilities.currentExtent.height }; const bool swapchainCreatable = { surfaceSize.width >= capabilities.minImageExtent.width && surfaceSize.width <= capabilities.maxImageExtent.width && surfaceSize.width > 0 && surfaceSize.height >= capabilities.minImageExtent.height && surfaceSize.height <= capabilities.maxImageExtent.height && surfaceSize.height > 0 }; // cleanup old vector oldImageReadySs = imageReadySs; imageReadySs.clear(); if( oldSwapchain ){ {VkResult errorCode = vkDeviceWaitIdle( device ); RESULT_HANDLER( errorCode, "vkDeviceWaitIdle" );} // fences might be in unsignaled state, so kill them too to get fresh signaled killFences( device, submissionFences ); // semaphores might be in signaled state, so kill them too to get fresh unsignaled killSemaphores( device, renderDoneSs ); // kill imageReadySs later when oldSwapchain is destroyed // only reset + later reuse already allocated and create new only if needed {VkResult errorCode = vkResetCommandPool( device, commandPool, 0 ); RESULT_HANDLER( errorCode, "vkResetCommandPool" );} killPipeline( device, pipeline ); killFramebuffers( device, framebuffers ); killSwapchainImageViews( device, swapchainImageViews ); // kill oldSwapchain later, after it is potentially used by vkCreateSwapchainKHR } // creating new if( swapchainCreatable ){ // reuses & destroys the oldSwapchain swapchain = initSwapchain( physicalDevice, device, surface, surfaceFormat, capabilities, graphicsQueueFamily, presentQueueFamily, oldSwapchain ); vector swapchainImages = enumerate( device, swapchain ); swapchainImageViews = initSwapchainImageViews( device, swapchainImages, surfaceFormat.format ); framebuffers = initFramebuffers( device, renderPass, swapchainImageViews, surfaceSize.width, surfaceSize.height ); pipeline = initPipeline( device, physicalDeviceProperties.limits, pipelineLayout, renderPass, vertexShader, fragmentShader, vertexBufferBinding, surfaceSize.width, surfaceSize.height ); acquireCommandBuffers( device, commandPool, static_cast( swapchainImages.size() ), commandBuffers ); for( size_t i = 0; i < swapchainImages.size(); ++i ){ beginCommandBuffer( commandBuffers[i] ); recordBeginRenderPass( commandBuffers[i], renderPass, framebuffers[i], ::clearColor, surfaceSize.width, surfaceSize.height ); recordBindPipeline( commandBuffers[i], pipeline ); recordBindVertexBuffer( commandBuffers[i], vertexBufferBinding, vertexBuffer ); recordDraw( commandBuffers[i], static_cast( triangle.size() ) ); recordEndRenderPass( commandBuffers[i] ); endCommandBuffer( commandBuffers[i] ); } imageReadySs = initSemaphores( device, maxInflightSubmissions ); // per https://github.com/KhronosGroup/Vulkan-Docs/issues/1150 need upto swapchain-image count renderDoneSs = initSemaphores( device, swapchainImages.size()); submissionFences = initFences( device, maxInflightSubmissions, VK_FENCE_CREATE_SIGNALED_BIT ); // signaled fence means previous execution finished, so we start rendering presignaled submissionNr = 0; } if( oldSwapchain ){ killSwapchain( device, oldSwapchain ); // per current spec, we can't really be sure these are not used :/ at least kill them after the swapchain // https://github.com/KhronosGroup/Vulkan-Docs/issues/152 killSemaphores( device, oldImageReadySs ); } return swapchain != VK_NULL_HANDLE; }; // Finally, rendering! Yay! const std::function render = [&](){ assert( swapchain ); // should be always true; should have yielded CPU if false // vkAcquireNextImageKHR produces unsafe semaphore that needs extra cleanup. Track that with this variable. bool unsafeSemaphore = false; try{ // remove oldest frame from being in flight before starting new one // refer to doc/, which talks about the cycle of how the synch primitives are (re)used here {VkResult errorCode = vkWaitForFences( device, 1, &submissionFences[submissionNr], VK_TRUE, UINT64_MAX ); RESULT_HANDLER( errorCode, "vkWaitForFences" );} {VkResult errorCode = vkResetFences( device, 1, &submissionFences[submissionNr] ); RESULT_HANDLER( errorCode, "vkResetFences" );} unsafeSemaphore = true; uint32_t nextSwapchainImageIndex = getNextImageIndex( device, swapchain, imageReadySs[submissionNr] ); unsafeSemaphore = false; submitToQueue( graphicsQueue, commandBuffers[nextSwapchainImageIndex], imageReadySs[submissionNr], renderDoneSs[nextSwapchainImageIndex], submissionFences[submissionNr] ); present( presentQueue, swapchain, nextSwapchainImageIndex, renderDoneSs[nextSwapchainImageIndex] ); submissionNr = (submissionNr + 1) % maxInflightSubmissions; } catch( VulkanResultException ex ){ if( ex.result == VK_SUBOPTIMAL_KHR || ex.result == VK_ERROR_OUT_OF_DATE_KHR ){ if( unsafeSemaphore && ex.result == VK_SUBOPTIMAL_KHR ){ cleanupUnsafeSemaphore( graphicsQueue, imageReadySs[submissionNr] ); // no way to sanitize vkQueuePresentKHR semaphores, really } recreateSwapchain(); // we need to start over... render(); } else throw; } }; setSizeEventHandler( recreateSwapchain ); setPaintEventHandler( render ); // Finally start the main message loop (and so render too) showWindow( window ); int exitStatus = messageLoop( window ); // proper Vulkan cleanup VkResult errorCode = vkDeviceWaitIdle( device ); RESULT_HANDLER( errorCode, "vkDeviceWaitIdle" ); // kill swapchain killSemaphores( device, renderDoneSs ); // imageReadySs killed after the swapchain // command buffers killed with pool killPipeline( device, pipeline ); killFramebuffers( device, framebuffers ); killSwapchainImageViews( device, swapchainImageViews ); killSwapchain( device, swapchain ); // per current spec, we can't really be sure these are not used :/ at least kill them after the swapchain // https://github.com/KhronosGroup/Vulkan-Docs/issues/152 killSemaphores( device, imageReadySs ); // kill vulkan killFences( device, submissionFences ); killCommandPool( device, commandPool ); killBuffer( device, vertexBuffer ); killMemory( device, vertexBufferMemory ); killPipelineLayout( device, pipelineLayout ); killShaderModule( device, fragmentShader ); killShaderModule( device, vertexShader ); killRenderPass( device, renderPass ); killDevice( device ); killSurface( instance, surface ); killWindow( window ); #if VULKAN_VALIDATION killDebug( instance, debugHandle ); #endif killInstance( instance ); return exitStatus; } catch( VulkanResultException vkE ){ logger << "ERROR: Terminated due to an uncaught VkResult exception: " << vkE.file << ":" << vkE.line << ":" << vkE.func << "() " << vkE.source << "() returned " << to_string( vkE.result ) << std::endl; return EXIT_FAILURE; } catch( const char* e ){ logger << "ERROR: Terminated due to an uncaught exception: " << e << std::endl; return EXIT_FAILURE; } catch( string e ){ logger << "ERROR: Terminated due to an uncaught exception: " << e << std::endl; return EXIT_FAILURE; } catch( std::exception e ){ logger << "ERROR: Terminated due to an uncaught exception: " << e.what() << std::endl; return EXIT_FAILURE; } catch( ... ){ logger << "ERROR: Terminated due to an unrecognized uncaught exception." << std::endl; return EXIT_FAILURE; } #if defined(_WIN32) && !defined(_CONSOLE) int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ){ return helloTriangle(); } #else int main(){ return helloTriangle(); } #endif // Implementation ////////////////////////////////////////////////////////////////////////////////// bool isLayerSupported( const char* layer, const vector& supportedLayers ){ const auto isSupportedPred = [layer]( const VkLayerProperties& prop ) -> bool{ return std::strcmp( layer, prop.layerName ) == 0; }; return std::any_of( supportedLayers.begin(), supportedLayers.end(), isSupportedPred ); } bool isExtensionSupported( const char* extension, const vector& supportedExtensions ){ const auto isSupportedPred = [extension]( const VkExtensionProperties& prop ) -> bool{ return std::strcmp( extension, prop.extensionName ) == 0; }; return std::any_of( supportedExtensions.begin(), supportedExtensions.end(), isSupportedPred ); } vector checkInstanceLayerSupport( const vector& requestedLayers, const vector& supportedLayers ){ vector compiledLayerList; for( const auto layer : requestedLayers ){ if( isLayerSupported( layer, supportedLayers ) ) compiledLayerList.push_back( layer ); else logger << "WARNING: Requested layer " << layer << " is not supported. It will not be enabled." << std::endl; } return compiledLayerList; } vector checkInstanceLayerSupport( const vector& optionalLayers ){ return checkInstanceLayerSupport( optionalLayers, enumerate() ); } vector getSupportedInstanceExtensions( const vector& providingLayers ){ auto supportedExtensions = enumerate(); for( const auto pl : providingLayers ){ const auto providedExtensions = enumerate( pl ); supportedExtensions.insert( supportedExtensions.end(), providedExtensions.begin(), providedExtensions.end() ); } return supportedExtensions; } vector getSupportedDeviceExtensions( const VkPhysicalDevice physDevice, const vector& providingLayers ){ auto supportedExtensions = enumerate( physDevice ); for( const auto pl : providingLayers ){ const auto providedExtensions = enumerate( physDevice, pl ); supportedExtensions.insert( supportedExtensions.end(), providedExtensions.begin(), providedExtensions.end() ); } return supportedExtensions; } bool checkExtensionSupport( const vector& extensions, const vector& supportedExtensions ){ bool allSupported = true; for( const auto extension : extensions ){ if( !isExtensionSupported( extension, supportedExtensions ) ){ allSupported = false; logger << "WARNING: Requested extension " << extension << " is not supported. Trying to enable it will likely fail." << std::endl; } } return allSupported; } bool checkDeviceExtensionSupport( const VkPhysicalDevice physDevice, const vector& extensions, const vector& providingLayers ){ return checkExtensionSupport( extensions, getSupportedDeviceExtensions( physDevice, providingLayers ) ); } VkInstance initInstance( const vector& layers, const vector& extensions ){ const VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, // pNext ::appName, // Nice to meetcha, and what's your name driver? 0, // app version nullptr, // engine name 0, // engine version VK_API_VERSION_1_0 // this app is written against the Vulkan 1.0 spec }; #if VULKAN_VALIDATION // in effect during vkCreateInstance and vkDestroyInstance duration (because callback object cannot be created without instance) const VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo{ VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, nullptr, // pNext translateFlags( ::debugSeverity, ::debugType ), ::genericDebugReportCallback, nullptr // pUserData }; const VkDebugUtilsMessengerCreateInfoEXT debugUtilsCreateInfo = { VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, nullptr, // pNext 0, // flags debugSeverity, debugType, ::genericDebugUtilsCallback, nullptr // pUserData }; bool debugUtils = std::find_if( extensions.begin(), extensions.end(), [](const char* e){ return std::strcmp( e, VK_EXT_DEBUG_UTILS_EXTENSION_NAME ) == 0; } ) != extensions.end(); bool debugReport = std::find_if( extensions.begin(), extensions.end(), [](const char* e){ return std::strcmp( e, VK_EXT_DEBUG_REPORT_EXTENSION_NAME ) == 0; } ) != extensions.end(); if( !debugUtils && !debugReport ) throw "VULKAN_VALIDATION is enabled but neither VK_EXT_debug_utils nor VK_EXT_debug_report extension is being enabled!"; const void* debugpNext = debugUtils ? (void*)&debugUtilsCreateInfo : (void*)&debugReportCreateInfo; #endif const VkInstanceCreateInfo instanceInfo{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, #if VULKAN_VALIDATION debugpNext, #else nullptr, // pNext #endif 0, // flags - reserved for future use &appInfo, static_cast( layers.size() ), layers.data(), static_cast( extensions.size() ), extensions.data() }; VkInstance instance; const VkResult errorCode = vkCreateInstance( &instanceInfo, nullptr, &instance ); RESULT_HANDLER( errorCode, "vkCreateInstance" ); loadInstanceExtensionsCommands( instance, extensions ); return instance; } void killInstance( const VkInstance instance ){ unloadInstanceExtensionsCommands( instance ); vkDestroyInstance( instance, nullptr ); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool isPresentationSupported( const VkPhysicalDevice physDevice, const uint32_t queueFamily, const VkSurfaceKHR surface ){ VkBool32 supported; const VkResult errorCode = vkGetPhysicalDeviceSurfaceSupportKHR( physDevice, queueFamily, surface, &supported ); RESULT_HANDLER( errorCode, "vkGetPhysicalDeviceSurfaceSupportKHR" ); return supported == VK_TRUE; } bool isPresentationSupported( const VkPhysicalDevice physDevice, const VkSurfaceKHR surface ){ uint32_t qfCount; vkGetPhysicalDeviceQueueFamilyProperties( physDevice, &qfCount, nullptr ); for( uint32_t qf = 0; qf < qfCount; ++qf ){ if( isPresentationSupported( physDevice, qf, surface ) ) return true; } return false; } VkPhysicalDevice getPhysicalDevice( const VkInstance instance, const VkSurfaceKHR surface ){ vector devices = enumerate( instance ); if( surface ){ for( auto it = devices.begin(); it != devices.end(); ){ const auto& pd = *it; if( !isPresentationSupported( pd, surface ) ) it = devices.erase( it ); else ++it; } } if( devices.empty() ) throw string("ERROR: No Physical Devices (GPUs) ") + (surface ? "with presentation support " : "") + "detected!"; else if( devices.size() == 1 ){ return devices[0]; } else{ for( const auto pd : devices ){ const VkPhysicalDeviceProperties pdp = getPhysicalDeviceProperties( pd ); if( pdp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ){ #if VULKAN_VALIDATION vkDebugReportMessageEXT( instance, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT, handleToUint64(instance), __LINE__, 1, u8"application", u8"More than one Physical Devices (GPU) found. Choosing the first dedicated one." ); #endif return pd; } } #if VULKAN_VALIDATION vkDebugReportMessageEXT( instance, VK_DEBUG_REPORT_WARNING_BIT_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT, handleToUint64(instance), __LINE__, 1, u8"application", u8"More than one Physical Devices (GPU) found. Just choosing the first one." ); #endif return devices[0]; } } VkPhysicalDeviceProperties getPhysicalDeviceProperties( VkPhysicalDevice physicalDevice ){ VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties( physicalDevice, &properties ); return properties; } VkPhysicalDeviceMemoryProperties getPhysicalDeviceMemoryProperties( VkPhysicalDevice physicalDevice ){ VkPhysicalDeviceMemoryProperties memoryInfo; vkGetPhysicalDeviceMemoryProperties( physicalDevice, &memoryInfo ); return memoryInfo; } vector getQueueFamilyProperties( VkPhysicalDevice device ){ uint32_t queueFamiliesCount; vkGetPhysicalDeviceQueueFamilyProperties( device, &queueFamiliesCount, nullptr ); vector queueFamilies( queueFamiliesCount ); vkGetPhysicalDeviceQueueFamilyProperties( device, &queueFamiliesCount, queueFamilies.data() ); return queueFamilies; } std::pair getQueueFamilies( const VkPhysicalDevice physDevice, const VkSurfaceKHR surface ){ constexpr uint32_t notFound = VK_QUEUE_FAMILY_IGNORED; const auto qfps = getQueueFamilyProperties( physDevice ); const auto findQueueFamilyThat = [&qfps, notFound](std::function predicate) -> uint32_t{ for( uint32_t qf = 0; qf < qfps.size(); ++qf ) if( predicate(qfps[qf], qf) ) return qf; return notFound; }; const auto isGraphics = [](const VkQueueFamilyProperties& props, const uint32_t = 0){ return props.queueFlags & VK_QUEUE_GRAPHICS_BIT; }; const auto isPresent = [=](const VkQueueFamilyProperties&, const uint32_t queueFamily){ return isPresentationSupported( physDevice, queueFamily, surface ); }; const auto isFusedGraphicsAndPresent = [=](const VkQueueFamilyProperties& props, const uint32_t queueFamily){ return isGraphics( props ) && isPresent( props, queueFamily ); }; uint32_t graphicsQueueFamily = notFound; uint32_t presentQueueFamily = notFound; if( ::forceSeparatePresentQueue ){ graphicsQueueFamily = findQueueFamilyThat( isGraphics ); const auto isSeparatePresent = [graphicsQueueFamily, isPresent](const VkQueueFamilyProperties& props, const uint32_t queueFamily){ return queueFamily != graphicsQueueFamily && isPresent( props, queueFamily ); }; presentQueueFamily = findQueueFamilyThat( isSeparatePresent ); } else{ graphicsQueueFamily = presentQueueFamily = findQueueFamilyThat( isFusedGraphicsAndPresent ); if( graphicsQueueFamily == notFound || presentQueueFamily == notFound ){ graphicsQueueFamily = findQueueFamilyThat( isGraphics ); presentQueueFamily = findQueueFamilyThat( isPresent ); } } if( graphicsQueueFamily == notFound ) throw "Cannot find a graphics queue family!"; if( presentQueueFamily == notFound ) throw "Cannot find a presentation queue family!"; return std::make_pair( graphicsQueueFamily, presentQueueFamily ); } VkDevice initDevice( const VkPhysicalDevice physDevice, const VkPhysicalDeviceFeatures& features, const uint32_t graphicsQueueFamily, const uint32_t presentQueueFamily, const vector& layers, const vector& extensions ){ checkDeviceExtensionSupport( physDevice, extensions, layers ); const float priority[] = {1.0f}; vector queues = { { VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, nullptr, // pNext 0, // flags graphicsQueueFamily, 1, // queue count priority } }; if( presentQueueFamily != graphicsQueueFamily ){ queues.push_back({ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, nullptr, // pNext 0, // flags presentQueueFamily, 1, // queue count priority }); } const VkDeviceCreateInfo deviceInfo{ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, nullptr, // pNext 0, // flags static_cast( queues.size() ), queues.data(), static_cast( layers.size() ), layers.data(), static_cast( extensions.size() ), extensions.data(), &features }; VkDevice device; const VkResult errorCode = vkCreateDevice( physDevice, &deviceInfo, nullptr, &device ); RESULT_HANDLER( errorCode, "vkCreateDevice" ); loadDeviceExtensionsCommands( device, extensions ); return device; } void killDevice( const VkDevice device ){ unloadDeviceExtensionsCommands( device ); vkDestroyDevice( device, nullptr ); } VkQueue getQueue( const VkDevice device, const uint32_t queueFamily, const uint32_t queueIndex ){ VkQueue queue; vkGetDeviceQueue( device, queueFamily, queueIndex, &queue ); return queue; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template< ResourceType resourceType, class T > VkMemoryRequirements getMemoryRequirements( VkDevice device, T resource ); template<> VkMemoryRequirements getMemoryRequirements< ResourceType::Buffer >( VkDevice device, VkBuffer buffer ){ VkMemoryRequirements memoryRequirements; vkGetBufferMemoryRequirements( device, buffer, &memoryRequirements ); return memoryRequirements; } template<> VkMemoryRequirements getMemoryRequirements< ResourceType::Image >( VkDevice device, VkImage image ){ VkMemoryRequirements memoryRequirements; vkGetImageMemoryRequirements( device, image, &memoryRequirements ); return memoryRequirements; } template< ResourceType resourceType, class T > void bindMemory( VkDevice device, T buffer, VkDeviceMemory memory, VkDeviceSize offset ); template<> void bindMemory< ResourceType::Buffer >( VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize offset ){ VkResult errorCode = vkBindBufferMemory( device, buffer, memory, offset ); RESULT_HANDLER( errorCode, "vkBindBufferMemory" ); } template<> void bindMemory< ResourceType::Image >( VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize offset ){ VkResult errorCode = vkBindImageMemory( device, image, memory, offset ); RESULT_HANDLER( errorCode, "vkBindImageMemory" ); } template< ResourceType resourceType, class T > VkDeviceMemory initMemory( VkDevice device, VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties, T resource, const std::vector& memoryTypePriority ){ const VkMemoryRequirements memoryRequirements = getMemoryRequirements( device, resource ); const auto indexToBit = []( const uint32_t index ){ return 0x1 << index; }; const uint32_t memoryTypeNotFound = UINT32_MAX; uint32_t memoryType = memoryTypeNotFound; for( const auto desiredMemoryType : memoryTypePriority ){ const uint32_t maxMemoryTypeCount = 32; for( uint32_t i = 0; memoryType == memoryTypeNotFound && i < maxMemoryTypeCount; ++i ){ if( memoryRequirements.memoryTypeBits & indexToBit(i) ){ if( (physicalDeviceMemoryProperties.memoryTypes[i].propertyFlags & desiredMemoryType) == desiredMemoryType ){ memoryType = i; } } } } if( memoryType == memoryTypeNotFound ) throw "Can't find compatible mappable memory for the resource"; VkMemoryAllocateInfo memoryInfo{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, nullptr, // pNext memoryRequirements.size, memoryType }; VkDeviceMemory memory; VkResult errorCode = vkAllocateMemory( device, &memoryInfo, nullptr, &memory ); RESULT_HANDLER( errorCode, "vkAllocateMemory" ); bindMemory( device, resource, memory, 0 /*offset*/ ); return memory; } void setMemoryData( VkDevice device, VkDeviceMemory memory, void* begin, size_t size ){ void* data; VkResult errorCode = vkMapMemory( device, memory, 0 /*offset*/, VK_WHOLE_SIZE, 0 /*flags - reserved*/, &data ); RESULT_HANDLER( errorCode, "vkMapMemory" ); memcpy( data, begin, size ); vkUnmapMemory( device, memory ); } void killMemory( VkDevice device, VkDeviceMemory memory ){ vkFreeMemory( device, memory, nullptr ); } VkBuffer initBuffer( VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage ){ VkBufferCreateInfo bufferInfo{ VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, // pNext 0, // flags size, usage, VK_SHARING_MODE_EXCLUSIVE, 0, // queue family count -- ignored for EXCLUSIVE nullptr // queue families -- ignored for EXCLUSIVE }; VkBuffer buffer; VkResult errorCode = vkCreateBuffer( device, &bufferInfo, nullptr, &buffer ); RESULT_HANDLER( errorCode, "vkCreateBuffer" ); return buffer; } void killBuffer( VkDevice device, VkBuffer buffer ){ vkDestroyBuffer( device, buffer, nullptr ); } VkImage initImage( VkDevice device, VkFormat format, uint32_t width, uint32_t height, VkSampleCountFlagBits samples, VkImageUsageFlags usage ){ VkExtent3D size{ width, height, 1 // depth }; VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr, // pNext 0, // flags VK_IMAGE_TYPE_2D, format, size, 1, // mipLevels 1, // arrayLayers samples, VK_IMAGE_TILING_OPTIMAL, usage, VK_SHARING_MODE_EXCLUSIVE, 0, // queueFamilyIndexCount -- ignored for EXCLUSIVE nullptr, // pQueueFamilyIndices -- ignored for EXCLUSIVE VK_IMAGE_LAYOUT_UNDEFINED }; VkImage image; VkResult errorCode = vkCreateImage( device, &ici, nullptr, &image ); RESULT_HANDLER( errorCode, "vkCreateImage" ); return image; } void killImage( VkDevice device, VkImage image ){ vkDestroyImage( device, image, nullptr ); } VkImageView initImageView( VkDevice device, VkImage image, VkFormat format ){ VkImageViewCreateInfo iciv{ VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, nullptr, // pNext 0, // flags image, VK_IMAGE_VIEW_TYPE_2D, format, { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }, { VK_IMAGE_ASPECT_COLOR_BIT, 0, // base mip-level VK_REMAINING_MIP_LEVELS, // level count 0, // base array layer VK_REMAINING_ARRAY_LAYERS // array layer count } }; VkImageView imageView; VkResult errorCode = vkCreateImageView( device, &iciv, nullptr, &imageView ); RESULT_HANDLER( errorCode, "vkCreateImageView" ); return imageView; } void killImageView( VkDevice device, VkImageView imageView ){ vkDestroyImageView( device, imageView, nullptr ); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // initSurface is platform dependent void killSurface( VkInstance instance, VkSurfaceKHR surface ){ vkDestroySurfaceKHR( instance, surface, nullptr ); } VkSurfaceFormatKHR getSurfaceFormat( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ){ const VkFormat preferredFormat1 = VK_FORMAT_B8G8R8A8_UNORM; const VkFormat preferredFormat2 = VK_FORMAT_B8G8R8A8_SRGB; vector formats = enumerate( physicalDevice, surface ); if( formats.empty() ) throw "No surface formats offered by Vulkan!"; if( formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED ){ formats[0].format = preferredFormat1; } VkSurfaceFormatKHR chosenFormat1 = {VK_FORMAT_UNDEFINED}; VkSurfaceFormatKHR chosenFormat2 = {VK_FORMAT_UNDEFINED}; for( auto f : formats ){ if( f.format == preferredFormat1 ){ chosenFormat1 = f; break; } if( f.format == preferredFormat2 ){ chosenFormat2 = f; } } if( chosenFormat1.format ) return chosenFormat1; else if( chosenFormat2.format ) return chosenFormat2; else return formats[0]; } VkSurfaceCapabilitiesKHR getSurfaceCapabilities( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ){ VkSurfaceCapabilitiesKHR capabilities; VkResult errorCode = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( physicalDevice, surface, &capabilities ); RESULT_HANDLER( errorCode, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR" ); return capabilities; } int selectedMode = 0; TODO( "Could use debug_report instead of log" ) VkPresentModeKHR getSurfacePresentMode( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface ){ vector modes = enumerate( physicalDevice, surface ); for( auto m : modes ){ if( m == ::presentMode ){ if( selectedMode != 0 ){ logger << "INFO: Your preferred present mode became supported. Switching to it.\n"; } selectedMode = 0; return m; } } for( auto m : modes ){ if( m == VK_PRESENT_MODE_FIFO_KHR ){ if( selectedMode != 1 ){ logger << "WARNING: Your preferred present mode is not supported. Switching to VK_PRESENT_MODE_FIFO_KHR.\n"; } selectedMode = 1; return m; } } TODO( "Workaround for bad (Intel Linux Mesa) drivers" ) if( modes.empty() ) throw "Bugged driver reports no supported present modes."; else{ if( selectedMode != 2 ){ logger << "WARNING: Bugged drivers. VK_PRESENT_MODE_FIFO_KHR not supported. Switching to whatever is.\n"; } selectedMode = 2; return modes[0]; } } VkSwapchainKHR initSwapchain( VkPhysicalDevice physicalDevice, VkDevice device, VkSurfaceKHR surface, VkSurfaceFormatKHR surfaceFormat, VkSurfaceCapabilitiesKHR capabilities, uint32_t graphicsQueueFamily, uint32_t presentQueueFamily, VkSwapchainKHR oldSwapchain ){ // we don't care as we are always setting alpha to 1.0 VkCompositeAlphaFlagBitsKHR compositeAlphaFlag; if( capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR ) compositeAlphaFlag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; else if( capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR ) compositeAlphaFlag = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR; else if( capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR ) compositeAlphaFlag = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR; else if( capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR ) compositeAlphaFlag = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; else throw "Unknown composite alpha reported."; // minImageCount + 1 seems a sensible default. It means 2 images should always be readily available without blocking. May lead to memory waste though if we care about that. uint32_t myMinImageCount = capabilities.minImageCount + 1; if( capabilities.maxImageCount ) myMinImageCount = std::min( myMinImageCount, capabilities.maxImageCount ); std::vector queueFamilies = { graphicsQueueFamily }; if(graphicsQueueFamily != presentQueueFamily) queueFamilies.push_back( presentQueueFamily ); VkSwapchainCreateInfoKHR swapchainInfo{ VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, nullptr, // pNext for extensions use 0, // flags - reserved for future use surface, myMinImageCount, // minImageCount surfaceFormat.format, surfaceFormat.colorSpace, capabilities.currentExtent, 1, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, // VkImage usage flags // It should be fine to just use CONCURRENT in the off chance we encounter the elusive GPU with separate present queue queueFamilies.size() > 1 ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE, static_cast( queueFamilies.size() ), queueFamilies.data(), capabilities.currentTransform, compositeAlphaFlag, getSurfacePresentMode( physicalDevice, surface ), VK_TRUE, // clipped oldSwapchain }; VkSwapchainKHR swapchain; VkResult errorCode = vkCreateSwapchainKHR( device, &swapchainInfo, nullptr, &swapchain ); RESULT_HANDLER( errorCode, "vkCreateSwapchainKHR" ); return swapchain; } void killSwapchain( VkDevice device, VkSwapchainKHR swapchain ){ vkDestroySwapchainKHR( device, swapchain, nullptr ); } uint32_t getNextImageIndex( VkDevice device, VkSwapchainKHR swapchain, VkSemaphore imageReadyS ){ uint32_t nextImageIndex; VkResult errorCode = vkAcquireNextImageKHR( device, swapchain, UINT64_MAX /* no timeout */, imageReadyS, VK_NULL_HANDLE, &nextImageIndex ); RESULT_HANDLER( errorCode, "vkAcquireNextImageKHR" ); return nextImageIndex; } vector initSwapchainImageViews( VkDevice device, vector images, VkFormat format ){ vector imageViews; for( auto image : images ){ VkImageView imageView = initImageView( device, image, format ); imageViews.push_back( imageView ); } return imageViews; } void killSwapchainImageViews( VkDevice device, vector& imageViews ){ for( auto imageView : imageViews ) vkDestroyImageView( device, imageView, nullptr ); imageViews.clear(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// VkRenderPass initRenderPass( VkDevice device, VkSurfaceFormatKHR surfaceFormat ){ VkAttachmentDescription colorAtachment{ 0, // flags surfaceFormat.format, VK_SAMPLE_COUNT_1_BIT, VK_ATTACHMENT_LOAD_OP_CLEAR, // color + depth VK_ATTACHMENT_STORE_OP_STORE, // color + depth VK_ATTACHMENT_LOAD_OP_DONT_CARE, // stencil VK_ATTACHMENT_STORE_OP_DONT_CARE, // stencil VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR }; VkAttachmentReference colorReference{ 0, // attachment VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; VkSubpassDescription subpass{ 0, // flags - reserved for future use VK_PIPELINE_BIND_POINT_GRAPHICS, 0, // input attachment count nullptr, // input attachments 1, // color attachment count &colorReference, // color attachments nullptr, // resolve attachments nullptr, // depth stencil attachment 0, // preserve attachment count nullptr // preserve attachments }; VkSubpassDependency srcDependency{ VK_SUBPASS_EXTERNAL, // srcSubpass 0, // dstSubpass VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // srcStageMask VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // dstStageMask 0, // srcAccessMask VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // dstAccessMask VK_DEPENDENCY_BY_REGION_BIT, // dependencyFlags }; // implicitly defined dependency would cover this, but let's replace it with this explicitly defined dependency! VkSubpassDependency dstDependency{ 0, // srcSubpass VK_SUBPASS_EXTERNAL, // dstSubpass VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // srcStageMask VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, // dstStageMask VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // srcAccessMask 0, // dstAccessMask VK_DEPENDENCY_BY_REGION_BIT, // dependencyFlags }; VkSubpassDependency dependencies[] = {srcDependency, dstDependency}; VkRenderPassCreateInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use 1, // attachment count &colorAtachment, // attachments 1, // subpass count &subpass, // subpasses 2, // dependency count dependencies // dependencies }; VkRenderPass renderPass; VkResult errorCode = vkCreateRenderPass( device, &renderPassInfo, nullptr, &renderPass ); RESULT_HANDLER( errorCode, "vkCreateRenderPass" ); return renderPass; } void killRenderPass( VkDevice device, VkRenderPass renderPass ){ vkDestroyRenderPass( device, renderPass, nullptr ); } vector initFramebuffers( VkDevice device, VkRenderPass renderPass, vector imageViews, uint32_t width, uint32_t height ){ vector framebuffers; for( auto imageView : imageViews ){ VkFramebufferCreateInfo framebufferInfo{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use renderPass, 1, // ImageView count &imageView, width, // width height, // height 1 // layers }; VkFramebuffer framebuffer; VkResult errorCode = vkCreateFramebuffer( device, &framebufferInfo, nullptr, &framebuffer ); RESULT_HANDLER( errorCode, "vkCreateFramebuffer" ); framebuffers.push_back( framebuffer ); } return framebuffers; } void killFramebuffers( VkDevice device, vector& framebuffers ){ for( auto framebuffer : framebuffers ) vkDestroyFramebuffer( device, framebuffer, nullptr ); framebuffers.clear(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// template vector loadBinaryFile( string filename ){ using std::ifstream; using std::istreambuf_iterator; vector data; try{ ifstream ifs; ifs.exceptions( ifs.failbit | ifs.badbit | ifs.eofbit ); ifs.open( filename, ifs.in | ifs.binary | ifs.ate ); const auto fileSize = static_cast( ifs.tellg() ); if( fileSize > 0 && (fileSize % sizeof(Type) == 0) ){ ifs.seekg( ifs.beg ); data.resize( fileSize / sizeof(Type) ); ifs.read( reinterpret_cast(data.data()), fileSize ); } } catch( ... ){ data.clear(); } return data; } VkShaderModule initShaderModule( VkDevice device, string filename ){ const auto shaderCode = loadBinaryFile( filename ); if( shaderCode.empty() ) throw "SPIR-V shader file " + filename + " is invalid or read failed!"; return initShaderModule( device, shaderCode ); } VkShaderModule initShaderModule( VkDevice device, const vector& shaderCode ){ VkShaderModuleCreateInfo shaderModuleInfo{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use shaderCode.size() * sizeof(uint32_t), shaderCode.data() }; VkShaderModule shaderModule; VkResult errorCode = vkCreateShaderModule( device, &shaderModuleInfo, nullptr, &shaderModule ); RESULT_HANDLER( errorCode, "vkCreateShaderModule" ); return shaderModule; } void killShaderModule( VkDevice device, VkShaderModule shaderModule ){ vkDestroyShaderModule( device, shaderModule, nullptr ); } VkPipelineLayout initPipelineLayout( VkDevice device ){ VkPipelineLayoutCreateInfo pipelineLayoutInfo{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use 0, // descriptorSetLayout count nullptr, 0, // push constant range count nullptr // push constant ranges }; VkPipelineLayout pipelineLayout; VkResult errorCode = vkCreatePipelineLayout( device, &pipelineLayoutInfo, nullptr, &pipelineLayout ); RESULT_HANDLER( errorCode, "vkCreatePipelineLayout" ); return pipelineLayout; } void killPipelineLayout( VkDevice device, VkPipelineLayout pipelineLayout ){ vkDestroyPipelineLayout( device, pipelineLayout, nullptr ); } VkPipeline initPipeline( VkDevice device, VkPhysicalDeviceLimits limits, VkPipelineLayout pipelineLayout, VkRenderPass renderPass, VkShaderModule vertexShader, VkShaderModule fragmentShader, const uint32_t vertexBufferBinding, uint32_t width, uint32_t height ){/* const VkPipelineShaderStageCreateInfo vertexShaderStage{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_SHADER_STAGE_VERTEX_BIT, vertexShader, u8"main", nullptr // SpecializationInfo - constants pushed to shader on pipeline creation time }; const VkPipelineShaderStageCreateInfo fragmentShaderStage{ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader, u8"main", nullptr // SpecializationInfo - constants pushed to shader on pipeline creation time };*/ VkPipelineShaderStageCreateInfo shaderStageStates[] = { { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_SHADER_STAGE_VERTEX_BIT, vertexShader, u8"main", nullptr // SpecializationInfo - constants pushed to shader on pipeline creation time }, { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader, u8"main", nullptr // SpecializationInfo - constants pushed to shader on pipeline creation time } }; const uint32_t vertexBufferStride = sizeof( Vertex2D_ColorF_pack ); if( vertexBufferBinding > limits.maxVertexInputBindings ){ throw string("Implementation does not allow enough input bindings. Needed: ") + to_string( vertexBufferBinding ) + string(", max: ") + to_string( limits.maxVertexInputBindings ); } if( vertexBufferStride > limits.maxVertexInputBindingStride ){ throw string("Implementation does not allow big enough vertex buffer stride: ") + to_string( vertexBufferStride ) + string(", max: ") + to_string( limits.maxVertexInputBindingStride ); } VkVertexInputBindingDescription vertexInputBindingDescription{ vertexBufferBinding, sizeof( Vertex2D_ColorF_pack ), // stride in bytes VK_VERTEX_INPUT_RATE_VERTEX }; vector inputBindingDescriptions = { vertexInputBindingDescription }; if( inputBindingDescriptions.size() > limits.maxVertexInputBindings ){ throw "Implementation does not allow enough input bindings."; } const uint32_t positionLocation = 0; const uint32_t colorLocation = 1; if( colorLocation >= limits.maxVertexInputAttributes ){ throw "Implementation does not allow enough input attributes."; } if( offsetof( Vertex2D_ColorF_pack, color ) > limits.maxVertexInputAttributeOffset ){ throw "Implementation does not allow sufficient attribute offset."; } VkVertexInputAttributeDescription positionInputAttributeDescription{ positionLocation, vertexBufferBinding, VK_FORMAT_R32G32_SFLOAT, offsetof( Vertex2D_ColorF_pack, position ) // offset in bytes }; VkVertexInputAttributeDescription colorInputAttributeDescription{ colorLocation, vertexBufferBinding, VK_FORMAT_R32G32B32_SFLOAT, offsetof( Vertex2D_ColorF_pack, color ) // offset in bytes }; vector inputAttributeDescriptions = { positionInputAttributeDescription, colorInputAttributeDescription }; VkPipelineVertexInputStateCreateInfo vertexInputState{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use static_cast( inputBindingDescriptions.size() ), inputBindingDescriptions.data(), static_cast( inputAttributeDescriptions.size() ), inputAttributeDescriptions.data() }; VkPipelineInputAssemblyStateCreateInfo inputAssemblyState{ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_FALSE // primitive restart }; VkViewport viewport{ 0.0f, // x 0.0f, // y static_cast( width ? width : 1 ), static_cast( height ? height : 1 ), 0.0f, // min depth 1.0f // max depth }; VkRect2D scissor{ {0, 0}, // offset {width, height} }; VkPipelineViewportStateCreateInfo viewportState{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use 1, // Viewport count &viewport, 1, // scisor count, &scissor }; VkPipelineRasterizationStateCreateInfo rasterizationState{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_FALSE, // depth clamp VK_FALSE, // rasterizer discard VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, VK_FALSE, // depth bias 0.0f, // bias constant factor 0.0f, // bias clamp 0.0f, // bias slope factor 1.0f // line width }; VkPipelineMultisampleStateCreateInfo multisampleState{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_SAMPLE_COUNT_1_BIT, VK_FALSE, // no sample shading 0.0f, // min sample shading - ignored if disabled nullptr, // sample mask VK_FALSE, // alphaToCoverage VK_FALSE // alphaToOne }; VkPipelineColorBlendAttachmentState blendAttachmentState{ VK_FALSE, // blending enabled? VK_BLEND_FACTOR_ZERO, // src blend factor -ignored? VK_BLEND_FACTOR_ZERO, // dst blend factor VK_BLEND_OP_ADD, // blend op VK_BLEND_FACTOR_ZERO, // src alpha blend factor VK_BLEND_FACTOR_ZERO, // dst alpha blend factor VK_BLEND_OP_ADD, // alpha blend op VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT // color write mask }; VkPipelineColorBlendStateCreateInfo colorBlendState{ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, nullptr, // pNext 0, // flags - reserved for future use VK_FALSE, // logic ops VK_LOGIC_OP_COPY, 1, // attachment count - must be same as color attachment count in renderpass subpass! &blendAttachmentState, {0.0f, 0.0f, 0.0f, 0.0f} // blend constants }; VkGraphicsPipelineCreateInfo pipelineInfo{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, nullptr, // pNext 0, // flags - e.g. disable optimization 2, // shader stages count - vertex and fragment shaderStageStates, &vertexInputState, &inputAssemblyState, nullptr, // tesselation &viewportState, &rasterizationState, &multisampleState, nullptr, // depth stencil &colorBlendState, nullptr, // dynamic state pipelineLayout, renderPass, 0, // subpass index in renderpass VK_NULL_HANDLE, // base pipeline -1 // base pipeline index }; VkPipeline pipeline; VkResult errorCode = vkCreateGraphicsPipelines( device, VK_NULL_HANDLE /* pipeline cache */, 1 /* info count */, &pipelineInfo, nullptr, &pipeline ); RESULT_HANDLER( errorCode, "vkCreateGraphicsPipelines" ); return pipeline; } void killPipeline( VkDevice device, VkPipeline pipeline ){ vkDestroyPipeline( device, pipeline, nullptr ); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setVertexData( VkDevice device, VkDeviceMemory memory, vector vertices ){ TODO( "Should be in Device Local memory instead" ) setMemoryData( device, memory, vertices.data(), sizeof( decltype(vertices)::value_type ) * vertices.size() ); } VkSemaphore initSemaphore( VkDevice device ){ const VkSemaphoreCreateInfo semaphoreInfo{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, // pNext 0 // flags - reserved for future use }; VkSemaphore semaphore; VkResult errorCode = vkCreateSemaphore( device, &semaphoreInfo, nullptr, &semaphore ); RESULT_HANDLER( errorCode, "vkCreateSemaphore" ); return semaphore; } vector initSemaphores( VkDevice device, size_t count ){ vector semaphores; std::generate_n( std::back_inserter( semaphores ), count, [device]{ return initSemaphore( device ); } ); return semaphores; } void killSemaphore( VkDevice device, VkSemaphore semaphore ){ vkDestroySemaphore( device, semaphore, nullptr ); } void killSemaphores( VkDevice device, vector& semaphores ){ for( const auto s : semaphores ) killSemaphore( device, s ); semaphores.clear(); } VkCommandPool initCommandPool( VkDevice device, const uint32_t queueFamily ){ const VkCommandPoolCreateInfo commandPoolInfo{ VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, nullptr, // pNext 0, // flags queueFamily }; VkCommandPool commandPool; VkResult errorCode = vkCreateCommandPool( device, &commandPoolInfo, nullptr, &commandPool ); RESULT_HANDLER( errorCode, "vkCreateCommandPool" ); return commandPool; } void killCommandPool( VkDevice device, VkCommandPool commandPool ){ vkDestroyCommandPool( device, commandPool, nullptr ); } VkFence initFence( const VkDevice device, const VkFenceCreateFlags flags = 0 ){ const VkFenceCreateInfo fci{ VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, nullptr, // pNext flags }; VkFence fence; VkResult errorCode = vkCreateFence( device, &fci, nullptr, &fence ); RESULT_HANDLER( errorCode, "vkCreateFence" ); return fence; } void killFence( const VkDevice device, const VkFence fence ){ vkDestroyFence( device, fence, nullptr ); } vector initFences( const VkDevice device, const size_t count, const VkFenceCreateFlags flags ){ vector fences; std::generate_n( std::back_inserter( fences ), count, [=]{return initFence( device, flags );} ); return fences; } void killFences( const VkDevice device, vector& fences ){ for( const auto f : fences ) killFence( device, f ); fences.clear(); } void acquireCommandBuffers( VkDevice device, VkCommandPool commandPool, uint32_t count, vector& commandBuffers ){ const auto oldSize = static_cast( commandBuffers.size() ); if( count > oldSize ){ VkCommandBufferAllocateInfo commandBufferInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, // pNext commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, count - oldSize // count }; commandBuffers.resize( count ); VkResult errorCode = vkAllocateCommandBuffers( device, &commandBufferInfo, &commandBuffers[oldSize] ); RESULT_HANDLER( errorCode, "vkAllocateCommandBuffers" ); } if( count < oldSize ) { vkFreeCommandBuffers( device, commandPool, oldSize - count, &commandBuffers[count] ); commandBuffers.resize( count ); } } void beginCommandBuffer( VkCommandBuffer commandBuffer ){ VkCommandBufferBeginInfo commandBufferInfo{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, // pNext // same buffer can be re-executed before it finishes from last submit VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, // flags nullptr // inheritance }; VkResult errorCode = vkBeginCommandBuffer( commandBuffer, &commandBufferInfo ); RESULT_HANDLER( errorCode, "vkBeginCommandBuffer" ); } void endCommandBuffer( VkCommandBuffer commandBuffer ){ VkResult errorCode = vkEndCommandBuffer( commandBuffer ); RESULT_HANDLER( errorCode, "vkEndCommandBuffer" ); } void recordBeginRenderPass( VkCommandBuffer commandBuffer, VkRenderPass renderPass, VkFramebuffer framebuffer, VkClearValue clearValue, uint32_t width, uint32_t height ){ VkRenderPassBeginInfo renderPassInfo{ VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, // pNext renderPass, framebuffer, {{0,0}, {width,height}}, //render area - offset plus extent 1, // clear value count &clearValue }; vkCmdBeginRenderPass( commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE ); } void recordEndRenderPass( VkCommandBuffer commandBuffer ){ vkCmdEndRenderPass( commandBuffer ); } void recordBindPipeline( VkCommandBuffer commandBuffer, VkPipeline pipeline ){ vkCmdBindPipeline( commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline ); } void recordBindVertexBuffer( VkCommandBuffer commandBuffer, const uint32_t vertexBufferBinding, VkBuffer vertexBuffer ){ VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers( commandBuffer, vertexBufferBinding, 1 /*binding count*/, &vertexBuffer, offsets ); } void recordDraw( VkCommandBuffer commandBuffer, const uint32_t vertexCount ){ vkCmdDraw( commandBuffer, vertexCount, 1 /*instance count*/, 0 /*first vertex*/, 0 /*first instance*/ ); } void submitToQueue( VkQueue queue, VkCommandBuffer commandBuffer, VkSemaphore imageReadyS, VkSemaphore renderDoneS, VkFence fence ){ const VkPipelineStageFlags psw = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; const VkSubmitInfo submit{ VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, // pNext 1, &imageReadyS, // wait semaphores &psw, // pipeline stages to wait for semaphore 1, &commandBuffer, 1, &renderDoneS // signal semaphores }; const VkResult errorCode = vkQueueSubmit( queue, 1 /*submit count*/, &submit, fence ); RESULT_HANDLER( errorCode, "vkQueueSubmit" ); } void present( VkQueue queue, VkSwapchainKHR swapchain, uint32_t swapchainImageIndex, VkSemaphore renderDoneS ){ const VkPresentInfoKHR presentInfo{ VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, nullptr, // pNext 1, &renderDoneS, // wait semaphores 1, &swapchain, &swapchainImageIndex, nullptr // pResults }; const VkResult errorCode = vkQueuePresentKHR( queue, &presentInfo ); RESULT_HANDLER( errorCode, "vkQueuePresentKHR" ); } // cleanup dangerous semaphore with signal pending from vkAcquireNextImageKHR (tie it to a specific queue) // https://github.com/KhronosGroup/Vulkan-Docs/issues/1059 void cleanupUnsafeSemaphore( VkQueue queue, VkSemaphore semaphore ){ VkPipelineStageFlags psw = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; const VkSubmitInfo submit{ VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, // pNext 1, &semaphore, // wait semaphores &psw, // pipeline stages to wait for semaphore 0, nullptr, // command buffers 0, nullptr // signal semaphores }; const VkResult errorCode = vkQueueSubmit( queue, 1 /*submit count*/, &submit, VK_NULL_HANDLE ); RESULT_HANDLER( errorCode, "vkQueueSubmit" ); } ================================================ FILE: src/LeanWindowsEnvironment.h ================================================ // As lean as possible Windows.h header global settings // Should be included as soon as possible (first line in translation unit preferably) #ifndef COMMON_LEAN_WINDOWS_ENVIRONMENT_H #define COMMON_LEAN_WINDOWS_ENVIRONMENT_H #ifdef _INC_WINDOWS #error Some Windows.h is already included before this file! #endif //_INC_WINDOWS #define WIN32_LEAN_AND_MEAN #define NOGDICAPMASKS //#define NOVIRTUALKEYCODES // need virtual keys e.g. VK_ESCAPE //#define NOWINMESSAGES // need window message definitions //#define NOWINSTYLES // need window styles to create windows #define NOSYSMETRICS #define NOMENUS //#define NOICONS // need icons to create window #define NOKEYSTATES //#define NOSYSCOMMANDS // need to manage LAlt key events #define NORASTEROPS //#define NOSHOWWINDOW // need to show windows #define OEMRESOURCE #define NOATOM #define NOCLIPBOARD //#define NOCOLOR // need default colors #define NOCTLMGR #define NODRAWTEXT #define NOGDI #define NOKERNEL //#define NOUSER // need it - contains MSG and lot other things //#define NONLS // need CP_UTF8 #define NOMB #define NOMEMMGR #define NOMETAFILE #define NOMINMAX //#define NOMSG // need MSG for event loop #define NOOPENFILE #define NOSCROLL #define NOSERVICE #define NOSOUND #define NOTEXTMETRIC #define NOWH //#define NOWINOFFSETS // need for GetWindowLongW #define NOCOMM #define NOKANJI #define NOHELP #define NOPROFILER #define NODEFERWINDOWPOS #define NOMCX // rip ANSI mode #ifndef UNICODE #define UNICODE #endif #endif //COMMON_LEAN_WINDOWS_ENVIRONMENT_H ================================================ FILE: src/Vertex.h ================================================ // Basic vertex data definitions #ifndef COMMON_VERTEX_H #define COMMON_VERTEX_H struct Vertex2D{ float position[2]; }; struct ColorF{ float color[3]; }; struct Vertex2D_ColorF_pack{ Vertex2D position; ColorF color; }; #endif //COMMON_VERTEX_H ================================================ FILE: src/VulkanEnvironment.h ================================================ // vulkan.h header global settings // Should be included as soon as possible (first line in translation unit preferably) #ifndef COMMON_VULKAN_ENVIRONMENT_H #define COMMON_VULKAN_ENVIRONMENT_H // detect premature vulkan.h #ifdef VULKAN_H_ #error "vulkan.h is included before VulkanEnvironment.h!" #endif //VULKAN_H_ #include "CompilerMessages.h" #define REQUIRED_HEADER_VERSION 176 #ifdef NDEBUG #ifndef VULKAN_VALIDATION #define VULKAN_VALIDATION 0 #endif #else #ifndef VULKAN_VALIDATION #define VULKAN_VALIDATION 1 #endif #endif #if defined(USE_PLATFORM_NONE) \ || defined(USE_PLATFORM_GLFW) \ || defined(VK_USE_PLATFORM_ANDROID_KHR) \ || defined(VK_USE_PLATFORM_MIR_KHR) \ || defined(VK_USE_PLATFORM_WAYLAND_KHR) \ || defined(VK_USE_PLATFORM_WIN32_KHR) \ || defined(VK_USE_PLATFORM_XCB_KHR) \ || defined(VK_USE_PLATFORM_XLIB_KHR) \ || defined(VK_USE_PLATFORM_IOS_MVK) \ || defined(VK_USE_PLATFORM_MACOS_MVK) \ || defined(VK_USE_PLATFORM_VI_NN) #define WSI_CHOSEN_EXTERNALLY #endif #if defined(_WIN32) && !defined(_CONSOLE) #include "LeanWindowsEnvironment.h" #include #endif // platform specific settings #if defined(__CYGWIN__) #ifndef WSI_CHOSEN_EXTERNALLY #define USE_PLATFORM_GLFW //#define VK_USE_PLATFORM_WIN32_KHR #endif #include "LeanWindowsEnvironment.h" // Windows.h settings must be first -- vulkan.h does include Windows.h #elif defined(__MINGW32__) #ifndef WSI_CHOSEN_EXTERNALLY #define USE_PLATFORM_GLFW //#define VK_USE_PLATFORM_WIN32_KHR #endif #include "LeanWindowsEnvironment.h" // Windows.h settings must be first -- vulkan.h does include Windows.h #elif defined(_WIN32) #ifndef WSI_CHOSEN_EXTERNALLY #define USE_PLATFORM_GLFW //#define VK_USE_PLATFORM_WIN32_KHR #endif #elif defined(__linux__) #ifndef WSI_CHOSEN_EXTERNALLY #define USE_PLATFORM_GLFW //#define VK_USE_PLATFORM_XCB_KHR //#define VK_USE_PLATFORM_XLIB_KHR //#define VK_USE_PLATFORM_WAYLAND_KHR #endif #else //#error "Unsupported OS platform." // caught in main.cpp instead #endif #endif //COMMON_VULKAN_ENVIRONMENT_H ================================================ FILE: src/VulkanIntrospection.h ================================================ // Introspection for Vulkan enums -- mostly to_string #ifndef COMMON_VULKAN_INTROSPECTION_H #define COMMON_VULKAN_INTROSPECTION_H #include #include #include template inline uint64_t handleToUint64(const PHANDLE_T *h) { return reinterpret_cast(h); } inline uint64_t handleToUint64(const uint64_t h) { return h; } const char* to_string( const VkResult r ){ switch( r ){ case VK_SUCCESS: return "VK_SUCCESS"; case VK_NOT_READY: return "VK_NOT_READY"; case VK_TIMEOUT: return "VK_TIMEOUT"; case VK_EVENT_SET: return "VK_EVENT_SET"; case VK_EVENT_RESET: return "VK_EVENT_RESET"; case VK_INCOMPLETE: return "VK_INCOMPLETE"; case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY"; case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR"; case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR"; case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR"; case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR"; case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR"; case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT"; case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV"; case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT"; case VK_ERROR_NOT_PERMITTED_EXT: return "VK_ERROR_NOT_PERMITTED_EXT"; case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT"; case VK_THREAD_IDLE_KHR: return "VK_THREAD_IDLE_KHR"; case VK_THREAD_DONE_KHR: return "VK_THREAD_DONE_KHR"; case VK_OPERATION_DEFERRED_KHR: return "VK_OPERATION_DEFERRED_KHR"; case VK_OPERATION_NOT_DEFERRED_KHR: return "VK_OPERATION_NOT_DEFERRED_KHR"; case VK_PIPELINE_COMPILE_REQUIRED_EXT: return "VK_PIPELINE_COMPILE_REQUIRED_EXT"; default: return "unrecognized VkResult code"; } } std::string to_string( const VkDebugReportObjectTypeEXT o ){ switch( o ){ case VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT: return "unknown"; case VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT: return "Instance"; case VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT: return "PhysicalDevice"; case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT: return "Device"; case VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT: return "Queue"; case VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT: return "Semaphore"; case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT: return "CommandBuffer"; case VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT: return "Fence"; case VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT: return "DeviceMemory"; case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT: return "Buffer"; case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT: return "Image"; case VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT: return "Event"; case VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT: return "QueryPool"; case VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT: return "BufferView"; case VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT: return "ImageView"; case VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT: return "ShaderModule"; case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT: return "PipelineCache"; case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT: return "PipelineLayout"; case VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT: return "RenderPass"; case VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT: return "Pipeline"; case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT: return "DescriptorSetLayout"; case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT: return "Sampler"; case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT: return "DescriptorPool"; case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT: return "DescriptorSet"; case VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT: return "Framebuffer"; case VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT: return "Command pool"; case VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT: return "SurfaceKHR"; case VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT: return "SwapchainKHR"; case VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT: return "DebugReportCallbackEXT"; case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT: return "DisplayKHR"; case VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT: return "DisplayModeKHR"; case VK_DEBUG_REPORT_OBJECT_TYPE_VALIDATION_CACHE_EXT_EXT: return "ValidationCacheEXT"; case VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_EXT: return "SamplerYcbcrConversion"; case VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT: return "DescriptorUpdateTemplate"; case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR_EXT: return "AccelerationStructureKHR"; case VK_DEBUG_REPORT_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV_EXT: return "AccelerationStructureNV"; default: return "unrecognized type"; } } std::string to_string( const VkObjectType o ){ switch( o ){ case VK_OBJECT_TYPE_UNKNOWN: return "unknown"; case VK_OBJECT_TYPE_INSTANCE: return "Instance"; case VK_OBJECT_TYPE_PHYSICAL_DEVICE: return "PhysicalDevice"; case VK_OBJECT_TYPE_DEVICE: return "Device"; case VK_OBJECT_TYPE_QUEUE: return "Queue"; case VK_OBJECT_TYPE_SEMAPHORE: return "Semaphore"; case VK_OBJECT_TYPE_COMMAND_BUFFER: return "CommandBuffer"; case VK_OBJECT_TYPE_FENCE: return "Fence"; case VK_OBJECT_TYPE_DEVICE_MEMORY: return "DeviceMemory"; case VK_OBJECT_TYPE_BUFFER: return "Buffer"; case VK_OBJECT_TYPE_IMAGE: return "Image"; case VK_OBJECT_TYPE_EVENT: return "Event"; case VK_OBJECT_TYPE_QUERY_POOL: return "QueryPool"; case VK_OBJECT_TYPE_BUFFER_VIEW: return "BufferView"; case VK_OBJECT_TYPE_IMAGE_VIEW: return "ImageView"; case VK_OBJECT_TYPE_SHADER_MODULE: return "ShaderModule"; case VK_OBJECT_TYPE_PIPELINE_CACHE: return "PipelineCache"; case VK_OBJECT_TYPE_PIPELINE_LAYOUT: return "PipelineLayout"; case VK_OBJECT_TYPE_RENDER_PASS: return "RenderPass"; case VK_OBJECT_TYPE_PIPELINE: return "Pipeline"; case VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT: return "DescriptorSetLayout"; case VK_OBJECT_TYPE_SAMPLER: return "Sampler"; case VK_OBJECT_TYPE_DESCRIPTOR_POOL: return "DescriptorPool"; case VK_OBJECT_TYPE_DESCRIPTOR_SET: return "DescriptorSet"; case VK_OBJECT_TYPE_FRAMEBUFFER: return "Framebuffer"; case VK_OBJECT_TYPE_COMMAND_POOL: return "Command pool"; case VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION: return "SamplerYcbcrConversion"; case VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE: return "DescriptorUpdateTemplateKHR"; case VK_OBJECT_TYPE_SURFACE_KHR: return "SurfaceKHR"; case VK_OBJECT_TYPE_SWAPCHAIN_KHR: return "SwapchainKHR"; case VK_OBJECT_TYPE_DISPLAY_KHR: return "DisplayKHR"; case VK_OBJECT_TYPE_DISPLAY_MODE_KHR: return "DisplayModeKHR"; case VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT: return "DebugReportCallbackEXT"; #ifdef VK_ENABLE_BETA_EXTENSIONS case VK_OBJECT_TYPE_VIDEO_SESSION_KHR: return "VideoSessionKHR"; case VK_OBJECT_TYPE_VIDEO_SESSION_PARAMETERS_KHR: return "VideoSessionParametersKHR"; #endif case VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT: return "DebugUtilsMessengerEXT"; case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR: return "AccelerationStructureKHR"; case VK_OBJECT_TYPE_VALIDATION_CACHE_EXT: return "ValidationCacheEXT"; case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV: return "AccelerationStructureNV"; case VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL: return "PerformanceConfigurationINTEL"; case VK_OBJECT_TYPE_DEFERRED_OPERATION_KHR: return "DeferredOperationKHR"; case VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV: return "IndirectCommandsLayoutNV"; case VK_OBJECT_TYPE_PRIVATE_DATA_SLOT_EXT: return "PrivateDataSlotEXT"; default: return "unrecognized type"; } } std::string dbrflags_to_string( VkDebugReportFlagsEXT msgFlags ){ std::string res; bool first = true; if( msgFlags & VK_DEBUG_REPORT_ERROR_BIT_EXT ){ if( !first ) res += " | "; res += "ERROR"; first = false; } if( msgFlags & VK_DEBUG_REPORT_WARNING_BIT_EXT ){ if( !first ) res += " | "; res += "WARNING"; first = false; } if( msgFlags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT ){ if( !first ) res += " | "; res += "PERFORMANCE"; first = false; } if( msgFlags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT ){ if( !first ) res += " | "; res += "Info"; first = false; } if( msgFlags & VK_DEBUG_REPORT_DEBUG_BIT_EXT ){ if( !first ) res += " | "; res += "Debug"; first = false; } VkDebugReportFlagsEXT known = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_DEBUG_BIT_EXT; if( msgFlags & ~known ){ if( !first ) res += " | "; res += "UNRECOGNIZED_FLAG"; first = false; } return res; } std::string dbuseverity_to_string( const VkDebugUtilsMessageSeverityFlagsEXT debugSeverity ){ std::string res; bool first = true; if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT ){ if( !first ) res += " | "; res += "ERROR"; first = false; } if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT ){ if( !first ) res += " | "; res += "WARNING"; first = false; } if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT ){ if( !first ) res += " | "; res += "Info"; first = false; } if( debugSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT ){ if( !first ) res += " | "; res += "Verbose"; first = false; } VkDebugUtilsMessageSeverityFlagsEXT known = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; if( debugSeverity & ~known ){ if( !first ) res += " | "; res += "UNRECOGNIZED_FLAG"; first = false; } return res; } std::string to_string( const VkDebugUtilsMessageSeverityFlagBitsEXT debugSeverity ){ return dbuseverity_to_string( debugSeverity ); } std::string dbutype_to_string( const VkDebugUtilsMessageTypeFlagsEXT debugType ){ std::string res; bool first = true; if( debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT ){ if( !first ) res += " | "; res += "GENERAL"; first = false; } if( debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT ){ if( !first ) res += " | "; res += "VALIDATION"; first = false; } if( debugType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT ){ if( !first ) res += " | "; res += "PERFORMANCE"; first = false; } VkDebugUtilsMessageSeverityFlagsEXT known = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; if( debugType & ~known ){ if( !first ) res += " | "; res += "UNRECOGNIZED_FLAG"; first = false; } return res; } std::string to_string_hex( const uint64_t n ){ std::stringstream ss; ss << std::hex << std::noshowbase << std::uppercase << n; return "0x" + ss.str(); } #endif //COMMON_VULKAN_INTROSPECTION_H ================================================ FILE: src/WSI/Glfw.h ================================================ // GLFW platform dependent WSI handling and event loop #ifndef COMMON_GLFW_WSI_H #define COMMON_GLFW_WSI_H #include #include #include #include #define GLFW_INCLUDE_NONE // Actually means include no OpenGL header #define GLFW_INCLUDE_VULKAN #include #include "CompilerMessages.h" #include "ErrorHandling.h" TODO( "Easier to use, but might prevent platform co-existence. Could be namespaced. Make all of this a class?" ) struct PlatformWindow{ GLFWwindow* window; }; std::string getPlatformSurfaceExtensionName(); int messageLoop( PlatformWindow window ); bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ); PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ); void killWindow( PlatformWindow window ); VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ); // killSurface() is not platform dependent void setSizeEventHandler( std::function newSizeEventHandler ); void setPaintEventHandler( std::function newPaintEventHandler ); void showWindow( PlatformWindow window ); // Implementation ////////////////////////////////// bool nullHandler(){ return false; } std::function sizeEventHandler = nullHandler; void setSizeEventHandler( std::function newSizeEventHandler ){ if( !newSizeEventHandler ) sizeEventHandler = nullHandler; sizeEventHandler = newSizeEventHandler; } std::function paintEventHandler = nullHandler; void setPaintEventHandler( std::function newPaintEventHandler ){ if( !newPaintEventHandler ) paintEventHandler = nullHandler; paintEventHandler = newPaintEventHandler; } struct GlfwError{ int error; std::string description; }; std::queue errors; void glfwErrorCallback( int error, const char* description ){ errors.push( {error, description} ); // postpone errors because I don't want to throw into C API } // just make the global glfw instialization\destruction static class GlfwSingleton{ static const GlfwSingleton glfwInstance; void destroyGlfw() noexcept{ glfwTerminate(); glfwSetErrorCallback( nullptr ); } ~GlfwSingleton(){ this->destroyGlfw(); } GlfwSingleton(){ glfwSetErrorCallback( glfwErrorCallback ); const auto success = glfwInit(); if( !success ){ glfwSetErrorCallback( nullptr ); throw "Trouble initializing GLFW!"; } if( !glfwVulkanSupported() ){ this->destroyGlfw(); throw "GLFW has trouble acquiring Vulkan support!"; } } }; const GlfwSingleton GlfwSingleton::glfwInstance; bool hasSwapchain = false; void showWindow( PlatformWindow window ){ glfwShowWindow( window.window ); // on linux there is no automatic initial size event -- need to call explicitly if( !hasSwapchain ) hasSwapchain = sizeEventHandler(); } std::string getPlatformSurfaceExtensionName(){ using std::string; uint32_t count; const char** extensions = glfwGetRequiredInstanceExtensions( &count ); // GLFW owns **extensions!!! if( !count || !extensions ) throw "GLFW failed to return required Vulkan extensions!"; if( count != 2 ) throw "GLFW returned unexpected number of required Vulkan extensions!"; if( extensions[0] != string( VK_KHR_SURFACE_EXTENSION_NAME ) ) throw VK_KHR_SURFACE_EXTENSION_NAME " is not a 1st required GLFW extension!"; string surfaceExtension = string( extensions[1] ); return string( extensions[1] ); } GLFWmonitor* getCurrentMonitor( GLFWwindow* window ){ using std::max; using std::min; int bestoverlap = 0; GLFWmonitor* bestmonitor = NULL; int wx, wy; glfwGetWindowPos( window, &wx, &wy ); int ww, wh; glfwGetWindowSize( window, &ww, &wh ); int nmonitors; auto monitors = glfwGetMonitors( &nmonitors ); for( int i = 0; i < nmonitors; ++i ){ auto mode = glfwGetVideoMode( monitors[i] ); int mw = mode->width; int mh = mode->height; int mx, my; glfwGetMonitorPos( monitors[i], &mx, &my ); int overlap = max(0, min(wx + ww, mx + mw) - max(wx, mx)) * max(0, min(wy + wh, my + mh) - max(wy, my)); if( bestoverlap < overlap ){ bestoverlap = overlap; bestmonitor = monitors[i]; } } return bestmonitor; } void windowSizeCallback( GLFWwindow*, int, int ) noexcept{ hasSwapchain = sizeEventHandler(); } void windowRefreshCallback( GLFWwindow* ) noexcept{ //logger << "refresh" << std::endl; if( hasSwapchain ) paintEventHandler(); } void toggleFullscreen( GLFWwindow* window ){ bool fullscreen = glfwGetWindowMonitor( window ) != NULL; TODO( "All of this needs to be made a class... death to the static!" ) static int wx = 100; static int wy = 100; static int ww = 800; static int wh = 800; if( !fullscreen ){ glfwGetWindowPos( window, &wx, &wy ); glfwGetWindowSize( window, &ww, &wh ); GLFWmonitor* monitor = getCurrentMonitor( window ); const GLFWvidmode* vmode = glfwGetVideoMode( monitor ); glfwSetWindowMonitor( window, monitor, 0, 0, vmode->width, vmode->height, vmode->refreshRate ); } else{ glfwSetWindowMonitor( window, NULL, wx, wy, ww, wh, GLFW_DONT_CARE ); } } TODO( "Fullscreen window seem to become unresponsive in Wayland session Ubuntu" ) void keyCallback( GLFWwindow* window, int key, int /*scancode*/, int action, int mods ) noexcept{ if( key == GLFW_KEY_ESCAPE && action == GLFW_PRESS ) glfwSetWindowShouldClose( window, GLFW_TRUE ); if( key == GLFW_KEY_ENTER && action == GLFW_PRESS && mods == GLFW_MOD_ALT ) toggleFullscreen( window ); } int messageLoop( PlatformWindow window ){ using std::to_string; while( errors.empty() && !glfwWindowShouldClose( window.window ) ){ if( hasSwapchain ) glfwPollEvents(); // do not block so I can paint else glfwWaitEvents(); // allows blocking if no events if( hasSwapchain ) paintEventHandler(); // repaint always even without OS repaint event } if( !errors.empty() ) throw to_string( errors.size() ) + " GLFW error(s) on backlog; 1st error: " + to_string( errors.front().error ) + ": " + errors.front().description; return EXIT_SUCCESS; } bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow ){ return glfwGetPhysicalDevicePresentationSupport( instance, device, queueFamilyIndex ) == GLFW_TRUE; } PlatformWindow initWindow( const std::string& name, const uint32_t canvasWidth, const uint32_t canvasHeight ){ std::string title = name + " -- GLFW"; glfwWindowHint( GLFW_CLIENT_API, GLFW_NO_API ); glfwWindowHint( GLFW_VISIBLE, GLFW_FALSE ); GLFWwindow* window = glfwCreateWindow( static_cast(canvasWidth), static_cast(canvasHeight), title.c_str(), NULL, NULL ); if( !window ) throw "Trouble creating GLFW window!"; glfwSetInputMode( window, GLFW_STICKY_KEYS, GLFW_TRUE ); glfwSetFramebufferSizeCallback( window, windowSizeCallback ); glfwSetWindowRefreshCallback( window, windowRefreshCallback ); glfwSetKeyCallback( window, keyCallback ); return { window }; } void killWindow( PlatformWindow window ){ glfwDestroyWindow( window.window ); } VkSurfaceKHR initSurface( const VkInstance instance, const PlatformWindow window ){ VkSurfaceKHR surface; const VkResult errorCode = glfwCreateWindowSurface( instance, window.window, nullptr, &surface ); RESULT_HANDLER( errorCode, "glfwCreateWindowSurface" ); return surface; } #endif //COMMON_GLFW_WSI_H ================================================ FILE: src/WSI/Wayland.h ================================================ // Wayland platform dependent WSI handling and event loop #ifndef COMMON_WAYLAND_WSI_H #define COMMON_WAYLAND_WSI_H #include #include #include #include #include #include #include #include #include #include #include #include "private/xdg-shell-client-protocol.h" #include "private/xdg-shell-client-protocol-private.inl" #include "CompilerMessages.h" #include "ErrorHandling.h" TODO( "Easier to use, but might prevent platform co-existence. Could be namespaced. Make all of this a class?" ) struct PlatformWindowImpl{ wl_display* display = nullptr; wl_registry* registry = nullptr; wl_compositor* compositor = nullptr; uint32_t compositorName; xdg_wm_base* wmBase = nullptr; uint32_t wmBaseName; wl_surface* surface = nullptr; xdg_surface* xdgSurface = nullptr; xdg_toplevel* toplevel = nullptr; wl_seat* seat = nullptr; uint32_t seatName; xkb_context* xkbContext = nullptr; xkb_keymap* keymap = nullptr; xkb_state* xkbState = nullptr; wl_keyboard* keyboard = nullptr; bool alt = false; wl_pointer* pointer = nullptr; bool quit = false; std::string title; bool inited = false; bool hasSwapchain = false; bool maximized = false, fullscreen = false; uint32_t width, height; uint32_t restoredWidth, restoredHeight; }; struct PlatformWindow{ std::shared_ptr impl; }; std::string getPlatformSurfaceExtensionName(){ return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; }; int messageLoop( PlatformWindow window ); bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ); PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ); void killWindow( PlatformWindow window ); VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ); // killSurface() is not platform dependent void setSizeEventHandler( std::function newSizeEventHandler ); void setPaintEventHandler( std::function newPaintEventHandler ); void showWindow( PlatformWindow window ); uint32_t getWindowWidth( PlatformWindow window ){ return window.impl->width; } uint32_t getWindowHeight( PlatformWindow window ){ return window.impl->height; } // Implementation ////////////////////////////////// bool nullHandler(){ return false; } std::function sizeEventHandler = nullHandler; void setSizeEventHandler( std::function newSizeEventHandler ){ if( !newSizeEventHandler ) sizeEventHandler = nullHandler; sizeEventHandler = newSizeEventHandler; } std::function paintEventHandler = nullHandler; void setPaintEventHandler( std::function newPaintEventHandler ){ if( !newPaintEventHandler ) paintEventHandler = nullHandler; paintEventHandler = newPaintEventHandler; } bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ){ return vkGetPhysicalDeviceWaylandPresentationSupportKHR( device, queueFamilyIndex, window.impl->display ) == VK_TRUE; } void registryGlobalHandler( void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version ) noexcept{ //logger << "registry: " << interface << std::endl; PlatformWindowImpl* window = (PlatformWindowImpl*)data; if( std::strcmp( interface, "wl_compositor" ) == 0 && !window->compositor ){ window->compositor = (wl_compositor*)wl_registry_bind( registry, name, &wl_compositor_interface, 1 ); window->compositorName = name; } else if( std::strcmp( interface, "xdg_wm_base" ) == 0 && !window->wmBase ){ window->wmBase = (xdg_wm_base*)wl_registry_bind( registry, name, &xdg_wm_base_interface, 1 ); window->wmBaseName = name; } else if( std::strcmp( interface, "wl_seat" ) == 0 && !window->seat ){ window->seat = (wl_seat*)wl_registry_bind( registry, name, &wl_seat_interface, 1 ); window->seatName = name; } TODO( "Add Wayland server side decorations when supported." ); } void registryGlobalRemoveHandler( void* data, wl_registry* registry, uint32_t name ) noexcept{ //logger << "registry remove " << name << std::endl; PlatformWindowImpl* window = (PlatformWindowImpl*)data; if( name == window->compositorName ) throw "Needed wl_compositor removed from registry."; if( name == window->wmBaseName ) throw "Needed xdg_wm_base removed from registry."; if( name == window->seatName ) throw "Needed wl_seat removed from registry."; } void seatCapsHandler( void* data, wl_seat* seat, uint32_t caps ) noexcept{ //logger << "wl_seat caps: " << caps << std::endl; RUNTIME_ASSERT( caps & WL_SEAT_CAPABILITY_POINTER, "Obtaining pointer wl_seat" ); RUNTIME_ASSERT( caps & WL_SEAT_CAPABILITY_KEYBOARD, "Obtaining keyboard wl_seat" ); } void keyboardKeymapHandler( void* data, wl_keyboard* keyboard, uint32_t format, int32_t fd, uint32_t size ) noexcept{ //logger << "wl_keyboard format: " << format << ", keymap size: " << size << std::endl; RUNTIME_ASSERT( format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Obtaining XKB V1 keymap" ); PlatformWindowImpl* wnd = (PlatformWindowImpl*)data; assert( wnd->xkbContext ); xkb_state_unref( wnd->xkbState ); xkb_keymap_unref( wnd->keymap ); const char* keymapBuffer = (const char*)mmap( nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0 ); RUNTIME_ASSERT( keymapBuffer != MAP_FAILED, "mmap of keymap") wnd->keymap = xkb_keymap_new_from_string( wnd->xkbContext, keymapBuffer, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS ); RUNTIME_ASSERT( wnd->keymap, "xkb_keymap_new_from_string" ); {const auto err = munmap( (void*)keymapBuffer, size ); RUNTIME_ASSERT( !err, "munmap of keymap" );} {const auto err = close( fd ); RUNTIME_ASSERT( !err, "close of keymap fd" );} wnd->xkbState = xkb_state_new( wnd->keymap ); } void keyboardKeyHandler( void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state ) noexcept{ //logger << "wl_keyboard key: key=" << key << ", state=" << state << std::endl; PlatformWindowImpl* wnd = (PlatformWindowImpl*)data; const xkb_keycode_t keycode = key + 8; // +8 per Wayland spec const xkb_keysym_t keysym = xkb_state_key_get_one_sym( wnd->xkbState, keycode ); if( state == WL_KEYBOARD_KEY_STATE_PRESSED ){ switch( keysym ){ case XKB_KEY_Escape: wnd->quit = true; break; case XKB_KEY_Alt_L: wnd->alt = true; break; case XKB_KEY_Return: if( wnd->alt ){ if( wnd->fullscreen ) xdg_toplevel_unset_fullscreen( wnd->toplevel ); else xdg_toplevel_set_fullscreen( wnd->toplevel, nullptr ); } break; } } else if( state == WL_KEYBOARD_KEY_STATE_RELEASED ){ switch( keysym ){ case XKB_KEY_Alt_L: wnd->alt = false; break; } } } void pointerButtonHandler( void* data, wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state ){ PlatformWindowImpl* wnd = (PlatformWindowImpl*)data; if( state == WL_POINTER_BUTTON_STATE_PRESSED ){ switch( button ){ case BTN_LEFT: xdg_toplevel_move( wnd->toplevel, wnd->seat, serial ); break; case BTN_RIGHT: xdg_toplevel_resize( wnd->toplevel, wnd->seat, serial, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT ); break; } } } void xdgSurfaceConfigureHandler( void* data, xdg_surface* xdg_surface, uint32_t serial ) noexcept{ //logger << "xdg_surface config" << std::endl; PlatformWindowImpl* window = (PlatformWindowImpl*)data; assert( window->inited ); xdg_surface_ack_configure( xdg_surface, serial ); } const char* state_to_string( uint32_t state ){ switch( state ){ case XDG_TOPLEVEL_STATE_MAXIMIZED: return "maximized"; case XDG_TOPLEVEL_STATE_FULLSCREEN: return "fullscreen"; case XDG_TOPLEVEL_STATE_RESIZING: return "resizing"; case XDG_TOPLEVEL_STATE_ACTIVATED: return "activated"; // v2: case XDG_TOPLEVEL_STATE_TILED_LEFT: return "tiled_left"; case XDG_TOPLEVEL_STATE_TILED_RIGHT: return "tiled_right"; case XDG_TOPLEVEL_STATE_TILED_TOP: return "tiled_top"; case XDG_TOPLEVEL_STATE_TILED_BOTTOM: return "tiled_bottom"; default: return "unrecognized state"; } } bool hasState( const std::vector& states, const uint32_t state ){ for( const auto s : states ) if( s == state ) return true; return false; } void toplevelConfigureHandler( void* data, xdg_toplevel* toplevel, int32_t width, int32_t height, wl_array* states ) noexcept{ //logger << "toplevel config: w=" << width << ", h=" << height << ", states={"; std::vector statesv; for( uint32_t* state = (uint32_t*)states->data; (const uint8_t*) state < ((const uint8_t*) states->data + states->size); ++state ){ statesv.push_back( *state ); } /* bool first = true; for( const auto state : statesv ){ if( !first ) logger << ", "; else first = false; logger << state_to_string( state ); } logger << "}\n"; */ PlatformWindowImpl* wnd = (PlatformWindowImpl*)data; assert( wnd->inited ); if( width != wnd->width || height != wnd->height ){ if( !(width == 0 && height == 0) ){ wnd->width = static_cast( width ); wnd->height = static_cast( height ); if( !hasState( statesv, XDG_TOPLEVEL_STATE_MAXIMIZED ) && !hasState( statesv, XDG_TOPLEVEL_STATE_FULLSCREEN ) ){ wnd->restoredWidth = static_cast( width ); wnd->restoredHeight = static_cast( height ); } } else{ wnd->width = wnd->restoredWidth; wnd->height = wnd->restoredHeight; } wnd->hasSwapchain = sizeEventHandler(); } if( hasState( statesv, XDG_TOPLEVEL_STATE_FULLSCREEN ) ) wnd->fullscreen = true; else wnd->fullscreen = false; if( hasState( statesv, XDG_TOPLEVEL_STATE_MAXIMIZED ) ) wnd->maximized = true; else wnd->maximized = false; } void toplevelCloseHandler( void* data, xdg_toplevel* xdg_toplevel ) noexcept{ //logger << "toplevel close" << std::endl; PlatformWindowImpl* window = (PlatformWindowImpl*)data; window->quit = true; } void wmBasePingHandler( void* data, xdg_wm_base* xdg_wm_base, uint32_t serial ) noexcept{ //logger << "wm_base ping" << std::endl; xdg_wm_base_pong( xdg_wm_base, serial ); } const wl_registry_listener registryListener = { registryGlobalHandler, registryGlobalRemoveHandler }; const wl_seat_listener seatListener = { seatCapsHandler, [](void*, wl_seat*, const char*){} }; TODO( "Should probably implement some of these callbacks."); const wl_keyboard_listener keyboardListener = { keyboardKeymapHandler, [](void*, wl_keyboard*, uint32_t, wl_surface*, wl_array*){}, [](void*, wl_keyboard*, uint32_t, wl_surface*){}, keyboardKeyHandler, [](void*, wl_keyboard*, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t){} }; const wl_pointer_listener pointerListener = { [](void*, wl_pointer*, uint32_t, wl_surface*, wl_fixed_t, wl_fixed_t){}, [](void*, wl_pointer*, uint32_t, wl_surface*){}, [](void*, wl_pointer*, uint32_t, wl_fixed_t, wl_fixed_t){}, pointerButtonHandler, [](void*, wl_pointer*, uint32_t, uint32_t, wl_fixed_t){} }; const xdg_wm_base_listener xdgWmBaseListerner = { wmBasePingHandler }; const xdg_surface_listener xdgSurfaceListener = { xdgSurfaceConfigureHandler }; const xdg_toplevel_listener xdgToplevelListener = { toplevelConfigureHandler, toplevelCloseHandler }; PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ){ auto wnd = std::make_shared(); wnd->width = canvasWidth; wnd->restoredWidth = canvasWidth; wnd->height = canvasHeight; wnd->restoredHeight = canvasHeight; wnd->display = wl_display_connect( nullptr ); RUNTIME_ASSERT( wnd->display, "wl_display_connect" ); wnd->registry = wl_display_get_registry( wnd->display ); RUNTIME_ASSERT( wnd->registry, "wl_display_get_registry" ); {const auto err = wl_registry_add_listener( wnd->registry, &::registryListener, wnd.get() ); RUNTIME_ASSERT( !err, "wl_registry_add_listener" );} {const auto dispatched = wl_display_roundtrip( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_roundtrip" );} // dispatch should be redundant to the roundtrip, but I like it explicit {const auto dispatched = wl_display_dispatch_pending( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_dispatch_pending" );} RUNTIME_ASSERT( wnd->compositor, "wl_registry_bind(\"wl_compositor\")" ); RUNTIME_ASSERT( wnd->wmBase, "wl_registry_bind(\"xdg_wm_base\")" ); RUNTIME_ASSERT( wnd->seat, "wl_registry_bind(\"wl_seat\")" ); wnd->xkbContext = xkb_context_new( XKB_CONTEXT_NO_FLAGS ); RUNTIME_ASSERT( wnd->xkbContext, "xkb_context_new" ); {const auto err = wl_seat_add_listener( wnd->seat, &seatListener, wnd.get() ); RUNTIME_ASSERT( !err, "wl_seat_add_listener" );} {const auto dispatched = wl_display_roundtrip( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_roundtrip" );} {const auto dispatched = wl_display_dispatch_pending( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_dispatch_pending" );} wnd->keyboard = wl_seat_get_keyboard( wnd->seat ); RUNTIME_ASSERT( wnd->keyboard, "wl_seat_get_keyboard" ); {const auto err = wl_keyboard_add_listener( wnd->keyboard, &keyboardListener, wnd.get() ); RUNTIME_ASSERT( !err, "wl_keyboard_add_listener" );} wnd->pointer = wl_seat_get_pointer( wnd->seat ); RUNTIME_ASSERT( wnd->pointer, "wl_seat_get_pointer" ); {const auto err = wl_pointer_add_listener( wnd->pointer, &pointerListener, wnd.get() ); RUNTIME_ASSERT( !err, "wl_pointer_add_listener" );} wnd->surface = wl_compositor_create_surface( wnd->compositor ); RUNTIME_ASSERT( wnd->surface, "wl_compositor_create_surface" ); {const auto err = xdg_wm_base_add_listener( wnd->wmBase, &xdgWmBaseListerner, wnd.get() ); RUNTIME_ASSERT( !err, "xdg_wm_base_add_listener" );} wnd->xdgSurface = xdg_wm_base_get_xdg_surface( wnd->wmBase, wnd->surface ); RUNTIME_ASSERT( wnd->xdgSurface, "xdg_wm_base_get_xdg_surface" ); wnd->toplevel = xdg_surface_get_toplevel( wnd->xdgSurface ); RUNTIME_ASSERT( wnd->toplevel, "xdg_surface_get_toplevel" ); {const auto err = xdg_surface_add_listener( wnd->xdgSurface, &xdgSurfaceListener, wnd.get() ); RUNTIME_ASSERT( !err, "xdg_surface_add_listener" );} {const auto err = xdg_toplevel_add_listener( wnd->toplevel, &xdgToplevelListener, wnd.get() ); RUNTIME_ASSERT( !err, "xdg_toplevel_add_listener" );} wnd->title = name + " -- Wayland"; xdg_toplevel_set_title( wnd->toplevel, "wnd->title.c_str()" ); TODO( "Shows \"Unknown\" in Ubuntu on taskbar.") wl_surface_commit( wnd->surface ); {const auto sent = wl_display_flush( wnd->display ); RUNTIME_ASSERT( sent != -1, "wl_display_flush" );} wnd->inited = true; return {wnd}; } void killWindow( PlatformWindow window ){ const auto wnd = window.impl; wnd->inited = false; xkb_state_unref( wnd->xkbState ); xkb_keymap_unref( wnd->keymap ); xkb_context_unref( wnd->xkbContext ); TODO( "Explicitly kill all Wayland stuff" ); wl_display_disconnect( wnd->display ); } void showWindow( PlatformWindow windowh ){ auto wnd = windowh.impl; // wait for first size event {const auto dispatched = wl_display_roundtrip( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_roundtrip" );} // dispatch should be redundant to the roundtrip, but I like it explicit {const auto dispatched = wl_display_dispatch_pending( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_dispatch_pending" );} assert( wnd->hasSwapchain ); // In Wayland window is mapped with first present paintEventHandler(); } int messageLoop( PlatformWindow windowh ){ auto wnd = windowh.impl; wnd->quit = false; while( !wnd->quit ){ {const auto dispatched = wl_display_dispatch_pending( wnd->display ); RUNTIME_ASSERT( dispatched != -1, "wl_display_dispatch_pending" );} if( wnd->hasSwapchain ){ TODO( "Use frame callback instead?" ); paintEventHandler(); } } return 0; } VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow windowh ){ auto wnd = windowh.impl; const VkWaylandSurfaceCreateInfoKHR surfaceInfo{ VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, // pNext for extensions use 0, // flags - reserved for future use wnd->display, wnd->surface }; VkSurfaceKHR surface; VkResult errorCode = vkCreateWaylandSurfaceKHR( instance, &surfaceInfo, nullptr, &surface ); RESULT_HANDLER( errorCode, "vkCreateWaylandSurfaceKHR" ); return surface; } #endif //COMMON_WAYLAND_WSI_H ================================================ FILE: src/WSI/Win32.h ================================================ // win32 platform dependent WSI handling and event loop #ifndef COMMON_WIN32_WSI_H #define COMMON_WIN32_WSI_H #include #include #include #include #include #include #include "CompilerMessages.h" #include "ErrorHandling.h" TODO( "Easier to use, but might prevent platform co-existence. Could be namespaced. Make all of this a class?" ) struct PlatformWindow{ HINSTANCE hInstance; HWND hWnd; }; DWORD windowedStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; DWORD windowedExStyle = WS_EX_OVERLAPPEDWINDOW; std::string getPlatformSurfaceExtensionName(){ return VK_KHR_WIN32_SURFACE_EXTENSION_NAME; }; int messageLoop( PlatformWindow window ); bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ); PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ); void killWindow( PlatformWindow window ); VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ); // killSurface() is not platform dependent void setSizeEventHandler( std::function newSizeEventHandler ); void setPaintEventHandler( std::function newPaintEventHandler ); void showWindow( PlatformWindow window ); // Implementation ////////////////////////////////// bool nullHandler(){ return false; } std::function sizeEventHandler = nullHandler; void setSizeEventHandler( std::function newSizeEventHandler ){ if( !newSizeEventHandler ) sizeEventHandler = nullHandler; sizeEventHandler = newSizeEventHandler; } std::function paintEventHandler = nullHandler; void setPaintEventHandler( std::function newPaintEventHandler ){ if( !newPaintEventHandler ) paintEventHandler = nullHandler; paintEventHandler = newPaintEventHandler; } void showWindow( PlatformWindow window ){ ShowWindow( window.hWnd, SW_SHOW ); SetForegroundWindow( window.hWnd ); } bool hasSwapchain = false; int messageLoop( PlatformWindow window ){ UNREFERENCED_PARAMETER( window ); MSG msg; BOOL ret = GetMessageW( &msg, NULL, 0, 0 ); for( ; ret; ret = GetMessageW( &msg, NULL, 0, 0 ) ){ TranslateMessage( &msg ); DispatchMessageW( &msg ); //dispatch to wndProc; ignore return from wndProc } if( ret == -1 ) throw std::string( "Trouble getting message: " ) + std::to_string( GetLastError() ); return static_cast( msg.wParam ); } void toggleFullscreen( HWND hWnd ){ TODO( "All of this needs to be made a class... death to the static!" ) static bool isFullscreen = false; static int wx = 100; static int wy = 100; static int ww = 800; static int wh = 800; DWORD style, exStyle; int x, y, width, height; if( !isFullscreen ){ windowedStyle = GetWindowLongW( hWnd, GWL_STYLE ); if( !windowedStyle && GetLastError() != ERROR_SUCCESS ) throw std::string( "Trouble setting window style: " ) + std::to_string( GetLastError() ); windowedExStyle = GetWindowLongW( hWnd, GWL_EXSTYLE ); if( !windowedExStyle && GetLastError() != ERROR_SUCCESS ) throw std::string( "Trouble setting window ex style: " ) + std::to_string( GetLastError() ); const DWORD fullscreenStyle = ::windowedStyle & ~(WS_CAPTION | WS_THICKFRAME); const DWORD fullscreenExStyle = ::windowedExStyle & ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); style = fullscreenStyle; exStyle = fullscreenExStyle; MONITORINFO monitorInfo; monitorInfo.cbSize = sizeof( MONITORINFO ); { const auto succ = GetMonitorInfo( MonitorFromWindow( hWnd, MONITOR_DEFAULTTOPRIMARY ), &monitorInfo ); if( !succ ) throw std::string( "Trouble getting window monitor info: " ) + std::to_string( GetLastError() ); } x = monitorInfo.rcMonitor.left; y = monitorInfo.rcMonitor.top; width = monitorInfo.rcMonitor.right - x; assert( width >= 0 ); height = monitorInfo.rcMonitor.bottom - y; assert( height >= 0 ); RECT wndRect; { const auto succ = GetWindowRect( hWnd, &wndRect ); if( !succ ) throw std::string( "Trouble getting window dimensions: " ) + std::to_string( GetLastError() ); } wx = wndRect.left; wy = wndRect.top; ww = wndRect.right - wx; wh = wndRect.bottom - wy; } else { style = ::windowedStyle; exStyle = ::windowedExStyle; x = wx; y = wy; width = ww; height = wh; } isFullscreen = !isFullscreen; assert( GetLastError() == ERROR_SUCCESS ); { const auto succ = SetWindowLongW( hWnd, GWL_STYLE, style ); if( !succ && GetLastError() != ERROR_SUCCESS ) throw std::string( "Trouble setting window style: " ) + std::to_string( GetLastError() ); } { const auto succ = SetWindowLongW( hWnd, GWL_EXSTYLE, exStyle ); if( !succ && GetLastError() != ERROR_SUCCESS ) throw std::string( "Trouble setting window ex style: " ) + std::to_string( GetLastError() ); } { const auto succ = SetWindowPos( hWnd, HWND_TOP, x, y, width, height, SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOZORDER ); if( !succ ) throw std::string( "Trouble resizing window to fullscreen: " ) + std::to_string( GetLastError() ); } } LRESULT CALLBACK wndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ switch( uMsg ){ case WM_CLOSE: PostQuitMessage( 0 ); return 0; case WM_ERASEBKGND: //logger << "erase\n"; //return DefWindowProc( hWnd, uMsg, wParam, lParam ); return 0; // background will be cleared by Vulkan in WM_PAINT instead //case WM_NCCALCSIZE: // //logger << "nccalcsize " << wParam << " " << lParam << endl; // return DefWindowProc( hWnd, uMsg, wParam, lParam ); case WM_SIZE: //logger << "size " << wParam << " " << LOWORD( lParam ) << "x" << HIWORD( lParam ) << std::endl; hasSwapchain = sizeEventHandler(); if( !hasSwapchain ) ValidateRect( hWnd, NULL ); // prevent WM_PAINT on minimized window return 0; case WM_PAINT: // sent after WM_SIZE -- react to this immediately to resize seamlessly //logger << "paint\n"; paintEventHandler(); //ValidateRect( hWnd, NULL ); // never validate so window always gets redrawn return 0; case WM_KEYDOWN: switch( wParam ){ case VK_ESCAPE: PostQuitMessage( 0 ); return 0; default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } case WM_SYSCOMMAND: switch( wParam ){ case SC_KEYMENU: if( lParam == VK_RETURN ){ // Alt-Enter without "no sysmenu hotkey exists" beep toggleFullscreen( hWnd ); return 0; } else return DefWindowProc( hWnd, uMsg, wParam, lParam ); default: return DefWindowProc( hWnd, uMsg, wParam, lParam ); } default: //logger << "unknown " << std::hex << std::showbase << uMsg << std::endl; return DefWindowProc( hWnd, uMsg, wParam, lParam ); } } bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow ){ UNREFERENCED_PARAMETER( instance ); return vkGetPhysicalDeviceWin32PresentationSupportKHR( device, queueFamilyIndex ) == VK_TRUE; } ATOM classAtom = 0; uint64_t classAtomRefCount = 0; ATOM initWindowClass(){ HINSTANCE hInstance = GetModuleHandleW( NULL ); if( classAtom == 0 ){ WNDCLASSEXW windowClass = { sizeof( WNDCLASSEXW ), // cbSize CS_OWNDC /*| CS_HREDRAW | CS_VREDRAW*/, // style -- some window behavior wndProc, // lpfnWndProc -- set event handler 0, // cbClsExtra -- set 0 extra bytes after class 0, // cbWndExtra -- set 0 extra bytes after class instance hInstance, // hInstance LoadIconW( NULL, IDI_APPLICATION ), // hIcon -- application icon LoadCursorW( NULL, IDC_ARROW ), // hCursor -- cursor inside NULL, //(HBRUSH)( COLOR_WINDOW + 1 ), // hbrBackground NULL, // lpszMenuName -- menu class name TEXT("vkwc"), // lpszClassName -- window class name/identificator LoadIconW( NULL, IDI_APPLICATION ) // hIconSm }; // register window class classAtom = RegisterClassExW( &windowClass ); if( !classAtom ){ throw std::string( "Trouble registering window class: " ) + std::to_string( GetLastError() ); } } ++classAtomRefCount; return classAtom; } PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ){ using std::string; using std::to_string; const string title = name + " -- Win32"; const auto titleU16Size = MultiByteToWideChar( CP_UTF8, 0, title.c_str(), -1, nullptr, 0 ); std::vector titleU16( titleU16Size ); const auto success = MultiByteToWideChar( CP_UTF8, 0, title.c_str(), -1, titleU16.data(), titleU16Size ); HINSTANCE hInstance = GetModuleHandleW( NULL ); ATOM wndClassAtom = initWindowClass(); // adjust size of window to contain given size canvas RECT windowRect = { 0, 0, static_cast(canvasWidth), static_cast(canvasHeight) }; if( !AdjustWindowRectEx( &windowRect, ::windowedStyle, FALSE, ::windowedExStyle ) ){ throw string( "Trouble adjusting window size: " ) + to_string( GetLastError() ); } // create window instance HWND hWnd = CreateWindowExW( ::windowedExStyle, MAKEINTATOM(wndClassAtom), titleU16.data(), ::windowedStyle, CW_USEDEFAULT, CW_USEDEFAULT, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, NULL, NULL, hInstance, NULL ); if( !hWnd ){ throw string( "Trouble creating window instance: " ) + to_string( GetLastError() ); } return { hInstance, hWnd }; } void killWindow( PlatformWindow window ){ using std::string; using std::to_string; if( !DestroyWindow( window.hWnd ) ) throw string( "Trouble destroying window instance: " ) + to_string( GetLastError() ); --classAtomRefCount; if( classAtomRefCount == 0 ){ if( !UnregisterClassW( MAKEINTATOM(classAtom), window.hInstance ) ){ throw string( "Trouble unregistering window class: " ) + to_string( GetLastError() ); } } } VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ){ VkWin32SurfaceCreateInfoKHR surfaceInfo{ VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, nullptr, // pNext for extensions use 0, // flags - reserved for future use window.hInstance, window.hWnd }; VkSurfaceKHR surface; VkResult errorCode = vkCreateWin32SurfaceKHR( instance, &surfaceInfo, nullptr, &surface ); RESULT_HANDLER( errorCode, "vkCreateWin32SurfaceKHR" ); return surface; } #endif //COMMON_WIN32_WSI_H ================================================ FILE: src/WSI/Xcb.h ================================================ // XCB linux platform dependent WSI handling and event loop #ifndef COMMON_XCB_WSI_H #define COMMON_XCB_WSI_H #include #include #include #include #include #include #include #include #include "CompilerMessages.h" #include "ErrorHandling.h" TODO( "Easier to use, but might prevent platform co-existence. Could be namespaced. Make all of this a class?" ) struct PlatformWindow{ xcb_connection_t* connection; xcb_window_t window; xcb_visualid_t visual_id; }; std::string getPlatformSurfaceExtensionName(){ return VK_KHR_XCB_SURFACE_EXTENSION_NAME; }; int messageLoop( PlatformWindow window ); bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ); PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ); void killWindow( PlatformWindow window ); VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ); // killSurface() is not platform dependent void setSizeEventHandler( std::function newSizeEventHandler ); void setPaintEventHandler( std::function newPaintEventHandler ); void showWindow( PlatformWindow window ); // Implementation ////////////////////////////////// bool nullHandler(){ return false; } std::function sizeEventHandler = nullHandler; void setSizeEventHandler( std::function newSizeEventHandler ){ if( !newSizeEventHandler ) sizeEventHandler = nullHandler; sizeEventHandler = newSizeEventHandler; } std::function paintEventHandler = nullHandler; void setPaintEventHandler( std::function newPaintEventHandler ){ if( !newPaintEventHandler ) paintEventHandler = nullHandler; paintEventHandler = newPaintEventHandler; } void showWindow( PlatformWindow window ){ xcb_map_window( window.connection, window.window ); xcb_flush( window.connection ); } int messageLoop( PlatformWindow window ){ int width = -1; int height = -1; bool hasSwapchain = false; bool quit = false; while( !quit ){ xcb_generic_event_t* e = hasSwapchain ? xcb_poll_for_event( window.connection ) : xcb_wait_for_event( window.connection ); if( e ){ switch( e->response_type & ~0x80 ){ case XCB_EXPOSE: paintEventHandler(); break; case XCB_CONFIGURE_NOTIFY:{ xcb_configure_notify_event_t* ce = (xcb_configure_notify_event_t*)e; if( ce->width != width || ce->height != height ){ width = ce->width; height = ce->height; hasSwapchain = sizeEventHandler(); } break; } case XCB_KEY_PRESS:{ xcb_key_press_event_t* kpe = (xcb_key_release_event_t*)e; xcb_key_symbols_t* keysyms = xcb_key_symbols_alloc( window.connection ); switch( xcb_key_press_lookup_keysym( keysyms, kpe, 0 ) ){ case XK_Escape: quit = true; } xcb_key_symbols_free( keysyms ); /* switch( kpe->detail ){ case 9: // ESC quit = true; } */ break; } case XCB_CLIENT_MESSAGE:{ xcb_client_message_event_t* cme = (xcb_client_message_event_t*)e; xcb_intern_atom_cookie_t wmdelCookie = xcb_intern_atom( window.connection, 1, 16, "WM_DELETE_WINDOW" ); xcb_intern_atom_reply_t* wmdelReply = xcb_intern_atom_reply( window.connection, wmdelCookie, 0 ); xcb_atom_t ATOM_WM_DELETE_WINDOW = wmdelReply->atom; free( wmdelReply ); if( cme->data.data32[0] == ATOM_WM_DELETE_WINDOW ){ quit = true; } break; } //default: // throw "Unrecognized event type!"; } free( e ); } else if( hasSwapchain ){ // no events pending paintEventHandler(); } } return 0; } bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ){ return vkGetPhysicalDeviceXcbPresentationSupportKHR( device, queueFamilyIndex, window.connection, window.visual_id ) == VK_TRUE; } std::string to_string_xcb_conn( int error ){ switch( error ){ case 0: return "XCB_CONN_SUCCESS"; case XCB_CONN_ERROR: return "XCB_CONN_ERROR"; case XCB_CONN_CLOSED_EXT_NOTSUPPORTED: return "XCB_CONN_CLOSED_EXT_NOTSUPPORTED"; case XCB_CONN_CLOSED_MEM_INSUFFICIENT: return "XCB_CONN_CLOSED_MEM_INSUFFICIENT"; case XCB_CONN_CLOSED_REQ_LEN_EXCEED: return "XCB_CONN_CLOSED_REQ_LEN_EXCEED"; case XCB_CONN_CLOSED_PARSE_ERR: return "XCB_CONN_CLOSED_PARSE_ERR"; case XCB_CONN_CLOSED_INVALID_SCREEN: return "XCB_CONN_CLOSED_INVALID_SCREEN"; default: return "unrecognized XCB connection error"; } } xcb_connection_t* initXcbConnection(){ xcb_connection_t* connection = xcb_connect( nullptr, nullptr ); int err = xcb_connection_has_error( connection ); if( err ){ xcb_disconnect( connection ); throw std::string( "Failed to create XCB connection: " ) + to_string_xcb_conn( err ); } return connection; } void killXcbConnection( xcb_connection_t* connection ){ xcb_disconnect( connection ); } PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ){ xcb_connection_t* connection = initXcbConnection(); xcb_screen_t* screen = xcb_setup_roots_iterator( xcb_get_setup( connection ) ).data; uint32_t masks = /* XCB_CW_BACK_PIXMAP | XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_BIT_GRAVITY | XCB_CW_WIN_GRAVITY | XCB_CW_BACKING_STORE | XCB_CW_BACKING_PLANES | XCB_CW_BACKING_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_SAVE_UNDER |*/ XCB_CW_EVENT_MASK /*| XCB_CW_DONT_PROPAGATE | XCB_CW_COLORMAP | XCB_CW_CURSOR*/ ; uint32_t values[] = { XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_STRUCTURE_NOTIFY }; xcb_window_t window = xcb_generate_id( connection ); xcb_create_window( connection, XCB_COPY_FROM_PARENT, window, screen->root, 0, 0, // x, y static_cast( canvasWidth ), static_cast( canvasHeight ), 1, //border_width XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, masks, values ); std::string title = name + " -- XCB"; xcb_change_property( connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, title.size(), title.c_str() ); xcb_change_property( connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_ICON_NAME, XCB_ATOM_STRING, 8, title.size(), title.c_str() ); xcb_intern_atom_cookie_t wmprotCookie = xcb_intern_atom( connection, 1, 12, "WM_PROTOCOLS" ); xcb_intern_atom_reply_t* wmprotReply = xcb_intern_atom_reply( connection, wmprotCookie, 0 ); xcb_atom_t ATOM_WM_PROTOCOLS = wmprotReply->atom; free( wmprotReply ); xcb_intern_atom_cookie_t wmdelCookie = xcb_intern_atom( connection, 0, 16, "WM_DELETE_WINDOW" ); xcb_intern_atom_reply_t* wmdelReply = xcb_intern_atom_reply( connection, wmdelCookie, 0 ); xcb_atom_t ATOM_WM_DELETE_WINDOW = wmdelReply->atom; free( wmdelReply ); // undocumented magic from random mailing list everybody seems to use xcb_change_property( connection, XCB_PROP_MODE_REPLACE, window, ATOM_WM_PROTOCOLS, XCB_ATOM_ATOM, 32, 1, &ATOM_WM_DELETE_WINDOW ); xcb_flush( connection ); return { connection, window, screen->root_visual }; } void killWindow( PlatformWindow window ){ xcb_destroy_window( window.connection, window.window ); xcb_flush( window.connection ); killXcbConnection( window.connection ); } VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ){ VkXcbSurfaceCreateInfoKHR surfaceInfo{ VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR, nullptr, // pNext for extensions use 0, // flags - reserved for future use window.connection, window.window }; VkSurfaceKHR surface; VkResult errorCode = vkCreateXcbSurfaceKHR( instance, &surfaceInfo, nullptr, &surface ); RESULT_HANDLER( errorCode, "vkCreateXcbSurfaceKHR" ); return surface; } #endif //COMMON_XCB_WSI_H ================================================ FILE: src/WSI/Xlib.h ================================================ // Xlib linux platform dependent WSI handling and event loop #ifndef COMMON_XLIB_WSI_H #define COMMON_XLIB_WSI_H #include #include #include #include #include #include "CompilerMessages.h" #include "ErrorHandling.h" TODO( "Easier to use, but might prevent platform co-existence. Could be namespaced. Make all of this a class?" ) struct PlatformWindow{ Display* display; Window window; VisualID visual_id; }; std::string getPlatformSurfaceExtensionName(){ return VK_KHR_XLIB_SURFACE_EXTENSION_NAME; }; PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ); void killWindow( PlatformWindow window ); bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ); VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ); // killSurface() is not platform dependent void setSizeEventHandler( std::function newSizeEventHandler ); void setPaintEventHandler( std::function newPaintEventHandler ); void showWindow( PlatformWindow window ); int messageLoop( PlatformWindow window ); // Implementation ////////////////////////////////// class XlibSingleton{ static const XlibSingleton xlibInstance; XlibSingleton(){ const auto success = XInitThreads(); if( !success ) throw "Failed to XInitThreads()."; } ~XlibSingleton(){} }; const XlibSingleton XlibSingleton::xlibInstance; Display* initXlibDisplay(){ Display* display = XOpenDisplay( nullptr ); if( !display ) throw "Failed to create Xlib Display*."; return display; } void killXlibDisplay( Display* display ){ XCloseDisplay( display ); } PlatformWindow initWindow( const std::string& name, uint32_t canvasWidth, uint32_t canvasHeight ){ Display* display = initXlibDisplay(); XLockDisplay( display ); Screen* screen = XDefaultScreenOfDisplay( display ); VisualID visual_id = XVisualIDFromVisual( XDefaultVisualOfScreen( screen ) ); Window root_window = XRootWindowOfScreen( screen ); //auto black = XBlackPixelOfScreen( screen ); //auto white = XWhitePixelOfScreen( screen ); unsigned long masks = /* CWBackPixmap | CWBackPixel | CWBorderPixmap | CWBorderPixel | CWBitGravity | CWWinGravity | CWBackingStore | CWBackingPlanes | CWBackingPixel | CWOverrideRedirect | CWSaveUnder |*/ CWEventMask /*| CWDontPropagate | CWColormap | CWCursor*/ ; XSetWindowAttributes values; values.event_mask = ExposureMask | KeyPressMask | StructureNotifyMask; Window window = XCreateWindow( display, root_window, 0, 0, // x, y static_cast( canvasWidth ), static_cast( canvasHeight ), 1, //border_width CopyFromParent, InputOutput, CopyFromParent, masks, &values ); const std::string title = name + " -- Xlib"; XStoreName( display, window, title.c_str() ); XSetIconName( display, window, title.c_str() ); Atom WM_DELETE_WINDOW = XInternAtom( display, "WM_DELETE_WINDOW", False ); XSetWMProtocols( display, window, &WM_DELETE_WINDOW, 1 ); XFlush( display ); XUnlockDisplay( display ); return { display, window, visual_id }; } void killWindow( PlatformWindow window ){ XLockDisplay( window.display ); XDestroyWindow( window.display, window.window ); XFlush( window.display ); XUnlockDisplay( window.display ); killXlibDisplay( window.display ); } bool platformPresentationSupport( VkInstance instance, VkPhysicalDevice device, uint32_t queueFamilyIndex, PlatformWindow window ){ return vkGetPhysicalDeviceXlibPresentationSupportKHR( device, queueFamilyIndex, window.display, window.visual_id ) == VK_TRUE; } VkSurfaceKHR initSurface( VkInstance instance, PlatformWindow window ){ VkXlibSurfaceCreateInfoKHR surfaceInfo{ VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, nullptr, // pNext for extensions use 0, // flags - reserved for future use window.display, window.window }; VkSurfaceKHR surface; VkResult errorCode = vkCreateXlibSurfaceKHR( instance, &surfaceInfo, nullptr, &surface ); RESULT_HANDLER( errorCode, "vkCreateXlibSurfaceKHR" ); return surface; } bool nullHandler(){ return false; } std::function sizeEventHandler = nullHandler; void setSizeEventHandler( std::function newSizeEventHandler ){ if( !newSizeEventHandler ) sizeEventHandler = nullHandler; sizeEventHandler = newSizeEventHandler; } std::function paintEventHandler = nullHandler; void setPaintEventHandler( std::function newPaintEventHandler ){ if( !newPaintEventHandler ) paintEventHandler = nullHandler; paintEventHandler = newPaintEventHandler; } void showWindow( PlatformWindow window ){ XLockDisplay( window.display ); XMapWindow( window.display, window.window ); XFlush( window.display ); XUnlockDisplay( window.display ); } int messageLoop( PlatformWindow window ){ int width = -1; int height = -1; bool hasSwapchain = false; bool quit = false; while( !quit ){ XEvent e; bool hasEvent = true; const auto always = []( Display*, XEvent*, XPointer ) -> Bool{return true;}; XLockDisplay( window.display ); if( hasSwapchain ) hasEvent = XCheckIfEvent( window.display, &e, always, nullptr ); else XNextEvent( window.display, &e ); XUnlockDisplay( window.display ); if( hasEvent ){ switch( e.type ){ case Expose: paintEventHandler(); break; case ConfigureNotify:{ XConfigureEvent ce = e.xconfigure; if( ce.width != width || ce.height != height ){ width = ce.width; height = ce.height; hasSwapchain = sizeEventHandler(); } break; } case KeyPress:{ XKeyPressedEvent kpe = e.xkey; XLockDisplay( window.display ); KeySym key = XLookupKeysym( &kpe, 0 ); XUnlockDisplay( window.display ); switch( key ){ case XK_Escape: quit = true; } break; } case ClientMessage:{ XClientMessageEvent cme = e.xclient; XLockDisplay( window.display ); Atom WM_DELETE_WINDOW = XInternAtom( window.display, "WM_DELETE_WINDOW", True ); XUnlockDisplay( window.display ); if( (Atom)cme.data.l[0] == WM_DELETE_WINDOW ){ quit = true; } break; } //default: // throw "Unrecognized event type!"; } } else if( hasSwapchain ){ paintEventHandler(); } } return 0; } #endif //COMMON_XLIB_WSI_H ================================================ FILE: src/WSI/private/xdg-shell-client-protocol-private.inl ================================================ /* Generated by wayland-scanner 1.16.0 */ /* * Copyright © 2008-2013 Kristian Høgsberg * Copyright © 2013 Rafael Antognolli * Copyright © 2013 Jasper St. Pierre * Copyright © 2010-2013 Intel Corporation * Copyright © 2015-2017 Samsung Electronics Co., Ltd * Copyright © 2015-2017 Red Hat Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include "wayland-util.h" #ifndef __has_attribute # define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ #endif #if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) #define WL_PRIVATE __attribute__ ((visibility("hidden"))) #else #define WL_PRIVATE #endif extern const struct wl_interface wl_output_interface; extern const struct wl_interface wl_seat_interface; extern const struct wl_interface wl_surface_interface; 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; static const struct wl_interface *types[] = { NULL, NULL, NULL, NULL, &xdg_positioner_interface, &xdg_surface_interface, &wl_surface_interface, &xdg_toplevel_interface, &xdg_popup_interface, &xdg_surface_interface, &xdg_positioner_interface, &xdg_toplevel_interface, &wl_seat_interface, NULL, NULL, NULL, &wl_seat_interface, NULL, &wl_seat_interface, NULL, NULL, &wl_output_interface, &wl_seat_interface, NULL, }; static const struct wl_message xdg_wm_base_requests[] = { { "destroy", "", types + 0 }, { "create_positioner", "n", types + 4 }, { "get_xdg_surface", "no", types + 5 }, { "pong", "u", types + 0 }, }; static const struct wl_message xdg_wm_base_events[] = { { "ping", "u", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { "xdg_wm_base", 2, 4, xdg_wm_base_requests, 1, xdg_wm_base_events, }; static const struct wl_message xdg_positioner_requests[] = { { "destroy", "", types + 0 }, { "set_size", "ii", types + 0 }, { "set_anchor_rect", "iiii", types + 0 }, { "set_anchor", "u", types + 0 }, { "set_gravity", "u", types + 0 }, { "set_constraint_adjustment", "u", types + 0 }, { "set_offset", "ii", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_positioner_interface = { "xdg_positioner", 2, 7, xdg_positioner_requests, 0, NULL, }; static const struct wl_message xdg_surface_requests[] = { { "destroy", "", types + 0 }, { "get_toplevel", "n", types + 7 }, { "get_popup", "n?oo", types + 8 }, { "set_window_geometry", "iiii", types + 0 }, { "ack_configure", "u", types + 0 }, }; static const struct wl_message xdg_surface_events[] = { { "configure", "u", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_surface_interface = { "xdg_surface", 2, 5, xdg_surface_requests, 1, xdg_surface_events, }; static const struct wl_message xdg_toplevel_requests[] = { { "destroy", "", types + 0 }, { "set_parent", "?o", types + 11 }, { "set_title", "s", types + 0 }, { "set_app_id", "s", types + 0 }, { "show_window_menu", "ouii", types + 12 }, { "move", "ou", types + 16 }, { "resize", "ouu", types + 18 }, { "set_max_size", "ii", types + 0 }, { "set_min_size", "ii", types + 0 }, { "set_maximized", "", types + 0 }, { "unset_maximized", "", types + 0 }, { "set_fullscreen", "?o", types + 21 }, { "unset_fullscreen", "", types + 0 }, { "set_minimized", "", types + 0 }, }; static const struct wl_message xdg_toplevel_events[] = { { "configure", "iia", types + 0 }, { "close", "", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { "xdg_toplevel", 2, 14, xdg_toplevel_requests, 2, xdg_toplevel_events, }; static const struct wl_message xdg_popup_requests[] = { { "destroy", "", types + 0 }, { "grab", "ou", types + 22 }, }; static const struct wl_message xdg_popup_events[] = { { "configure", "iiii", types + 0 }, { "popup_done", "", types + 0 }, }; WL_PRIVATE const struct wl_interface xdg_popup_interface = { "xdg_popup", 2, 2, xdg_popup_requests, 2, xdg_popup_events, }; ================================================ FILE: src/WSI/private/xdg-shell-client-protocol.h ================================================ /* Generated by wayland-scanner 1.16.0 */ #ifndef XDG_SHELL_CLIENT_PROTOCOL_H #define XDG_SHELL_CLIENT_PROTOCOL_H #include #include #include "wayland-client.h" #ifdef __cplusplus extern "C" { #endif /** * @page page_xdg_shell The xdg_shell protocol * @section page_ifaces_xdg_shell Interfaces * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces * - @subpage page_iface_xdg_positioner - child surface positioner * - @subpage page_iface_xdg_surface - desktop user interface surface base interface * - @subpage page_iface_xdg_toplevel - toplevel surface * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus * @section page_copyright_xdg_shell Copyright *
 *
 * Copyright © 2008-2013 Kristian Høgsberg
 * Copyright © 2013      Rafael Antognolli
 * Copyright © 2013      Jasper St. Pierre
 * Copyright © 2010-2013 Intel Corporation
 * Copyright © 2015-2017 Samsung Electronics Co., Ltd
 * Copyright © 2015-2017 Red Hat Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * 
*/ struct wl_output; struct wl_seat; struct wl_surface; struct xdg_popup; struct xdg_positioner; struct xdg_surface; struct xdg_toplevel; struct xdg_wm_base; /** * @page page_iface_xdg_wm_base xdg_wm_base * @section page_iface_xdg_wm_base_desc Description * * The xdg_wm_base interface is exposed as a global object enabling clients * to turn their wl_surfaces into windows in a desktop environment. It * defines the basic functionality needed for clients and the compositor to * create windows that can be dragged, resized, maximized, etc, as well as * creating transient windows such as popup menus. * @section page_iface_xdg_wm_base_api API * See @ref iface_xdg_wm_base. */ /** * @defgroup iface_xdg_wm_base The xdg_wm_base interface * * The xdg_wm_base interface is exposed as a global object enabling clients * to turn their wl_surfaces into windows in a desktop environment. It * defines the basic functionality needed for clients and the compositor to * create windows that can be dragged, resized, maximized, etc, as well as * creating transient windows such as popup menus. */ extern const struct wl_interface xdg_wm_base_interface; /** * @page page_iface_xdg_positioner xdg_positioner * @section page_iface_xdg_positioner_desc Description * * The xdg_positioner provides a collection of rules for the placement of a * child surface relative to a parent surface. Rules can be defined to ensure * the child surface remains within the visible area's borders, and to * specify how the child surface changes its position, such as sliding along * an axis, or flipping around a rectangle. These positioner-created rules are * constrained by the requirement that a child surface must intersect with or * be at least partially adjacent to its parent surface. * * See the various requests for details about possible rules. * * At the time of the request, the compositor makes a copy of the rules * specified by the xdg_positioner. Thus, after the request is complete the * xdg_positioner object can be destroyed or reused; further changes to the * object will have no effect on previous usages. * * For an xdg_positioner object to be considered complete, it must have a * non-zero size set by set_size, and a non-zero anchor rectangle set by * set_anchor_rect. Passing an incomplete xdg_positioner object when * positioning a surface raises an error. * @section page_iface_xdg_positioner_api API * See @ref iface_xdg_positioner. */ /** * @defgroup iface_xdg_positioner The xdg_positioner interface * * The xdg_positioner provides a collection of rules for the placement of a * child surface relative to a parent surface. Rules can be defined to ensure * the child surface remains within the visible area's borders, and to * specify how the child surface changes its position, such as sliding along * an axis, or flipping around a rectangle. These positioner-created rules are * constrained by the requirement that a child surface must intersect with or * be at least partially adjacent to its parent surface. * * See the various requests for details about possible rules. * * At the time of the request, the compositor makes a copy of the rules * specified by the xdg_positioner. Thus, after the request is complete the * xdg_positioner object can be destroyed or reused; further changes to the * object will have no effect on previous usages. * * For an xdg_positioner object to be considered complete, it must have a * non-zero size set by set_size, and a non-zero anchor rectangle set by * set_anchor_rect. Passing an incomplete xdg_positioner object when * positioning a surface raises an error. */ extern const struct wl_interface xdg_positioner_interface; /** * @page page_iface_xdg_surface xdg_surface * @section page_iface_xdg_surface_desc Description * * An interface that may be implemented by a wl_surface, for * implementations that provide a desktop-style user interface. * * It provides a base set of functionality required to construct user * interface elements requiring management by the compositor, such as * toplevel windows, menus, etc. The types of functionality are split into * xdg_surface roles. * * Creating an xdg_surface does not set the role for a wl_surface. In order * to map an xdg_surface, the client must create a role-specific object * using, e.g., get_toplevel, get_popup. The wl_surface for any given * xdg_surface can have at most one role, and may not be assigned any role * not based on xdg_surface. * * A role must be assigned before any other requests are made to the * xdg_surface object. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_surface state to take effect. * * Creating an xdg_surface from a wl_surface which has a buffer attached or * committed is a client error, and any attempts by a client to attach or * manipulate a buffer prior to the first xdg_surface.configure call must * also be treated as errors. * * Mapping an xdg_surface-based role surface is defined as making it * possible for the surface to be shown by the compositor. Note that * a mapped surface is not guaranteed to be visible once it is mapped. * * For an xdg_surface to be mapped by the compositor, the following * conditions must be met: * (1) the client has assigned an xdg_surface-based role to the surface * (2) the client has set and committed the xdg_surface state and the * role-dependent state to the surface * (3) the client has committed a buffer to the surface * * A newly-unmapped surface is considered to have met condition (1) out * of the 3 required conditions for mapping a surface if its role surface * has not been destroyed. * @section page_iface_xdg_surface_api API * See @ref iface_xdg_surface. */ /** * @defgroup iface_xdg_surface The xdg_surface interface * * An interface that may be implemented by a wl_surface, for * implementations that provide a desktop-style user interface. * * It provides a base set of functionality required to construct user * interface elements requiring management by the compositor, such as * toplevel windows, menus, etc. The types of functionality are split into * xdg_surface roles. * * Creating an xdg_surface does not set the role for a wl_surface. In order * to map an xdg_surface, the client must create a role-specific object * using, e.g., get_toplevel, get_popup. The wl_surface for any given * xdg_surface can have at most one role, and may not be assigned any role * not based on xdg_surface. * * A role must be assigned before any other requests are made to the * xdg_surface object. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_surface state to take effect. * * Creating an xdg_surface from a wl_surface which has a buffer attached or * committed is a client error, and any attempts by a client to attach or * manipulate a buffer prior to the first xdg_surface.configure call must * also be treated as errors. * * Mapping an xdg_surface-based role surface is defined as making it * possible for the surface to be shown by the compositor. Note that * a mapped surface is not guaranteed to be visible once it is mapped. * * For an xdg_surface to be mapped by the compositor, the following * conditions must be met: * (1) the client has assigned an xdg_surface-based role to the surface * (2) the client has set and committed the xdg_surface state and the * role-dependent state to the surface * (3) the client has committed a buffer to the surface * * A newly-unmapped surface is considered to have met condition (1) out * of the 3 required conditions for mapping a surface if its role surface * has not been destroyed. */ extern const struct wl_interface xdg_surface_interface; /** * @page page_iface_xdg_toplevel xdg_toplevel * @section page_iface_xdg_toplevel_desc Description * * This interface defines an xdg_surface role which allows a surface to, * among other things, set window-like properties such as maximize, * fullscreen, and minimize, set application-specific metadata like title and * id, and well as trigger user interactive operations such as interactive * resize and move. * * Unmapping an xdg_toplevel means that the surface cannot be shown * by the compositor until it is explicitly mapped again. * All active operations (e.g., move, resize) are canceled and all * attributes (e.g. title, state, stacking, ...) are discarded for * an xdg_toplevel surface when it is unmapped. * * Attaching a null buffer to a toplevel unmaps the surface. * @section page_iface_xdg_toplevel_api API * See @ref iface_xdg_toplevel. */ /** * @defgroup iface_xdg_toplevel The xdg_toplevel interface * * This interface defines an xdg_surface role which allows a surface to, * among other things, set window-like properties such as maximize, * fullscreen, and minimize, set application-specific metadata like title and * id, and well as trigger user interactive operations such as interactive * resize and move. * * Unmapping an xdg_toplevel means that the surface cannot be shown * by the compositor until it is explicitly mapped again. * All active operations (e.g., move, resize) are canceled and all * attributes (e.g. title, state, stacking, ...) are discarded for * an xdg_toplevel surface when it is unmapped. * * Attaching a null buffer to a toplevel unmaps the surface. */ extern const struct wl_interface xdg_toplevel_interface; /** * @page page_iface_xdg_popup xdg_popup * @section page_iface_xdg_popup_desc Description * * A popup surface is a short-lived, temporary surface. It can be used to * implement for example menus, popovers, tooltips and other similar user * interface concepts. * * A popup can be made to take an explicit grab. See xdg_popup.grab for * details. * * When the popup is dismissed, a popup_done event will be sent out, and at * the same time the surface will be unmapped. See the xdg_popup.popup_done * event for details. * * Explicitly destroying the xdg_popup object will also dismiss the popup and * unmap the surface. Clients that want to dismiss the popup when another * surface of their own is clicked should dismiss the popup using the destroy * request. * * A newly created xdg_popup will be stacked on top of all previously created * xdg_popup surfaces associated with the same xdg_toplevel. * * The parent of an xdg_popup must be mapped (see the xdg_surface * description) before the xdg_popup itself. * * The x and y arguments passed when creating the popup object specify * where the top left of the popup should be placed, relative to the * local surface coordinates of the parent surface. See * xdg_surface.get_popup. An xdg_popup must intersect with or be at least * partially adjacent to its parent surface. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_popup state to take effect. * @section page_iface_xdg_popup_api API * See @ref iface_xdg_popup. */ /** * @defgroup iface_xdg_popup The xdg_popup interface * * A popup surface is a short-lived, temporary surface. It can be used to * implement for example menus, popovers, tooltips and other similar user * interface concepts. * * A popup can be made to take an explicit grab. See xdg_popup.grab for * details. * * When the popup is dismissed, a popup_done event will be sent out, and at * the same time the surface will be unmapped. See the xdg_popup.popup_done * event for details. * * Explicitly destroying the xdg_popup object will also dismiss the popup and * unmap the surface. Clients that want to dismiss the popup when another * surface of their own is clicked should dismiss the popup using the destroy * request. * * A newly created xdg_popup will be stacked on top of all previously created * xdg_popup surfaces associated with the same xdg_toplevel. * * The parent of an xdg_popup must be mapped (see the xdg_surface * description) before the xdg_popup itself. * * The x and y arguments passed when creating the popup object specify * where the top left of the popup should be placed, relative to the * local surface coordinates of the parent surface. See * xdg_surface.get_popup. An xdg_popup must intersect with or be at least * partially adjacent to its parent surface. * * The client must call wl_surface.commit on the corresponding wl_surface * for the xdg_popup state to take effect. */ extern const struct wl_interface xdg_popup_interface; #ifndef XDG_WM_BASE_ERROR_ENUM #define XDG_WM_BASE_ERROR_ENUM enum xdg_wm_base_error { /** * given wl_surface has another role */ XDG_WM_BASE_ERROR_ROLE = 0, /** * xdg_wm_base was destroyed before children */ XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, /** * the client tried to map or destroy a non-topmost popup */ XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, /** * the client specified an invalid popup parent surface */ XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, /** * the client provided an invalid surface state */ XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, /** * the client provided an invalid positioner */ XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, }; #endif /* XDG_WM_BASE_ERROR_ENUM */ /** * @ingroup iface_xdg_wm_base * @struct xdg_wm_base_listener */ struct xdg_wm_base_listener { /** * check if the client is alive * * The ping event asks the client if it's still alive. Pass the * serial specified in the event back to the compositor by sending * a "pong" request back with the specified serial. See * xdg_wm_base.ping. * * Compositors can use this to determine if the client is still * alive. It's unspecified what will happen if the client doesn't * respond to the ping request, or in what timeframe. Clients * should try to respond in a reasonable amount of time. * * A compositor is free to ping in any way it wants, but a client * must always respond to any xdg_wm_base object it created. * @param serial pass this to the pong request */ void (*ping)(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial); }; /** * @ingroup iface_xdg_wm_base */ static inline int xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, const struct xdg_wm_base_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, (void (**)(void)) listener, data); } #define XDG_WM_BASE_DESTROY 0 #define XDG_WM_BASE_CREATE_POSITIONER 1 #define XDG_WM_BASE_GET_XDG_SURFACE 2 #define XDG_WM_BASE_PONG 3 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_PING_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 /** * @ingroup iface_xdg_wm_base */ #define XDG_WM_BASE_PONG_SINCE_VERSION 1 /** @ingroup iface_xdg_wm_base */ static inline void xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); } /** @ingroup iface_xdg_wm_base */ static inline void * xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); } static inline uint32_t xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) { return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); } /** * @ingroup iface_xdg_wm_base * * Destroy this xdg_wm_base object. * * Destroying a bound xdg_wm_base object while there are surfaces * still alive created by this xdg_wm_base object instance is illegal * and will result in a protocol error. */ static inline void xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) { wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_wm_base); } /** * @ingroup iface_xdg_wm_base * * Create a positioner object. A positioner object is used to position * surfaces relative to some parent surface. See the interface description * and xdg_surface.get_popup for details. */ static inline struct xdg_positioner * xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, NULL); return (struct xdg_positioner *) id; } /** * @ingroup iface_xdg_wm_base * * This creates an xdg_surface for the given surface. While xdg_surface * itself is not a role, the corresponding surface may only be assigned * a role extending xdg_surface, such as xdg_toplevel or xdg_popup. * * This creates an xdg_surface for the given surface. An xdg_surface is * used as basis to define a role to a given surface, such as xdg_toplevel * or xdg_popup. It also manages functionality shared between xdg_surface * based surface roles. * * See the documentation of xdg_surface for more details about what an * xdg_surface is and how it is used. */ static inline struct xdg_surface * xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, NULL, surface); return (struct xdg_surface *) id; } /** * @ingroup iface_xdg_wm_base * * A client must respond to a ping event with a pong request or * the client may be deemed unresponsive. See xdg_wm_base.ping. */ static inline void xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_wm_base, XDG_WM_BASE_PONG, serial); } #ifndef XDG_POSITIONER_ERROR_ENUM #define XDG_POSITIONER_ERROR_ENUM enum xdg_positioner_error { /** * invalid input provided */ XDG_POSITIONER_ERROR_INVALID_INPUT = 0, }; #endif /* XDG_POSITIONER_ERROR_ENUM */ #ifndef XDG_POSITIONER_ANCHOR_ENUM #define XDG_POSITIONER_ANCHOR_ENUM enum xdg_positioner_anchor { XDG_POSITIONER_ANCHOR_NONE = 0, XDG_POSITIONER_ANCHOR_TOP = 1, XDG_POSITIONER_ANCHOR_BOTTOM = 2, XDG_POSITIONER_ANCHOR_LEFT = 3, XDG_POSITIONER_ANCHOR_RIGHT = 4, XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, }; #endif /* XDG_POSITIONER_ANCHOR_ENUM */ #ifndef XDG_POSITIONER_GRAVITY_ENUM #define XDG_POSITIONER_GRAVITY_ENUM enum xdg_positioner_gravity { XDG_POSITIONER_GRAVITY_NONE = 0, XDG_POSITIONER_GRAVITY_TOP = 1, XDG_POSITIONER_GRAVITY_BOTTOM = 2, XDG_POSITIONER_GRAVITY_LEFT = 3, XDG_POSITIONER_GRAVITY_RIGHT = 4, XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, }; #endif /* XDG_POSITIONER_GRAVITY_ENUM */ #ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM #define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM /** * @ingroup iface_xdg_positioner * vertically resize the surface * * Resize the surface vertically so that it is completely unconstrained. */ enum xdg_positioner_constraint_adjustment { XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, }; #endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ #define XDG_POSITIONER_DESTROY 0 #define XDG_POSITIONER_SET_SIZE 1 #define XDG_POSITIONER_SET_ANCHOR_RECT 2 #define XDG_POSITIONER_SET_ANCHOR 3 #define XDG_POSITIONER_SET_GRAVITY 4 #define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 #define XDG_POSITIONER_SET_OFFSET 6 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 /** * @ingroup iface_xdg_positioner */ #define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 /** @ingroup iface_xdg_positioner */ static inline void xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); } /** @ingroup iface_xdg_positioner */ static inline void * xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); } static inline uint32_t xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) { return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); } /** * @ingroup iface_xdg_positioner * * Notify the compositor that the xdg_positioner will no longer be used. */ static inline void xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_positioner); } /** * @ingroup iface_xdg_positioner * * Set the size of the surface that is to be positioned with the positioner * object. The size is in surface-local coordinates and corresponds to the * window geometry. See xdg_surface.set_window_geometry. * * If a zero or negative size is set the invalid_input error is raised. */ static inline void xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_SIZE, width, height); } /** * @ingroup iface_xdg_positioner * * Specify the anchor rectangle within the parent surface that the child * surface will be placed relative to. The rectangle is relative to the * window geometry as defined by xdg_surface.set_window_geometry of the * parent surface. * * When the xdg_positioner object is used to position a child surface, the * anchor rectangle may not extend outside the window geometry of the * positioned child's parent surface. * * If a negative size is set the invalid_input error is raised. */ static inline void xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_ANCHOR_RECT, x, y, width, height); } /** * @ingroup iface_xdg_positioner * * Defines the anchor point for the anchor rectangle. The specified anchor * is used derive an anchor point that the child surface will be * positioned relative to. If a corner anchor is set (e.g. 'top_left' or * 'bottom_right'), the anchor point will be at the specified corner; * otherwise, the derived anchor point will be centered on the specified * edge, or in the center of the anchor rectangle if no edge is specified. */ static inline void xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_ANCHOR, anchor); } /** * @ingroup iface_xdg_positioner * * Defines in what direction a surface should be positioned, relative to * the anchor point of the parent surface. If a corner gravity is * specified (e.g. 'bottom_right' or 'top_left'), then the child surface * will be placed towards the specified gravity; otherwise, the child * surface will be centered over the anchor point on any axis that had no * gravity specified. */ static inline void xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_GRAVITY, gravity); } /** * @ingroup iface_xdg_positioner * * Specify how the window should be positioned if the originally intended * position caused the surface to be constrained, meaning at least * partially outside positioning boundaries set by the compositor. The * adjustment is set by constructing a bitmask describing the adjustment to * be made when the surface is constrained on that axis. * * If no bit for one axis is set, the compositor will assume that the child * surface should not change its position on that axis when constrained. * * If more than one bit for one axis is set, the order of how adjustments * are applied is specified in the corresponding adjustment descriptions. * * The default adjustment is none. */ static inline void xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, constraint_adjustment); } /** * @ingroup iface_xdg_positioner * * Specify the surface position offset relative to the position of the * anchor on the anchor rectangle and the anchor on the surface. For * example if the anchor of the anchor rectangle is at (x, y), the surface * has the gravity bottom|right, and the offset is (ox, oy), the calculated * surface position will be (x + ox, y + oy). The offset position of the * surface is the one used for constraint testing. See * set_constraint_adjustment. * * An example use case is placing a popup menu on top of a user interface * element, while aligning the user interface element of the parent surface * with some user interface element placed somewhere in the popup surface. */ static inline void xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) { wl_proxy_marshal((struct wl_proxy *) xdg_positioner, XDG_POSITIONER_SET_OFFSET, x, y); } #ifndef XDG_SURFACE_ERROR_ENUM #define XDG_SURFACE_ERROR_ENUM enum xdg_surface_error { XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, }; #endif /* XDG_SURFACE_ERROR_ENUM */ /** * @ingroup iface_xdg_surface * @struct xdg_surface_listener */ struct xdg_surface_listener { /** * suggest a surface change * * The configure event marks the end of a configure sequence. A * configure sequence is a set of one or more events configuring * the state of the xdg_surface, including the final * xdg_surface.configure event. * * Where applicable, xdg_surface surface roles will during a * configure sequence extend this event as a latched state sent as * events before the xdg_surface.configure event. Such events * should be considered to make up a set of atomically applied * configuration states, where the xdg_surface.configure commits * the accumulated state. * * Clients should arrange their surface for the new states, and * then send an ack_configure request with the serial sent in this * configure event at some point before committing the new surface. * * If the client receives multiple configure events before it can * respond to one, it is free to discard all but the last event it * received. * @param serial serial of the configure event */ void (*configure)(void *data, struct xdg_surface *xdg_surface, uint32_t serial); }; /** * @ingroup iface_xdg_surface */ static inline int xdg_surface_add_listener(struct xdg_surface *xdg_surface, const struct xdg_surface_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, (void (**)(void)) listener, data); } #define XDG_SURFACE_DESTROY 0 #define XDG_SURFACE_GET_TOPLEVEL 1 #define XDG_SURFACE_GET_POPUP 2 #define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 #define XDG_SURFACE_ACK_CONFIGURE 4 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 /** * @ingroup iface_xdg_surface */ #define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 /** @ingroup iface_xdg_surface */ static inline void xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); } /** @ingroup iface_xdg_surface */ static inline void * xdg_surface_get_user_data(struct xdg_surface *xdg_surface) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); } static inline uint32_t xdg_surface_get_version(struct xdg_surface *xdg_surface) { return wl_proxy_get_version((struct wl_proxy *) xdg_surface); } /** * @ingroup iface_xdg_surface * * Destroy the xdg_surface object. An xdg_surface must only be destroyed * after its role object has been destroyed. */ static inline void xdg_surface_destroy(struct xdg_surface *xdg_surface) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_surface); } /** * @ingroup iface_xdg_surface * * This creates an xdg_toplevel object for the given xdg_surface and gives * the associated wl_surface the xdg_toplevel role. * * See the documentation of xdg_toplevel for more details about what an * xdg_toplevel is and how it is used. */ static inline struct xdg_toplevel * xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, NULL); return (struct xdg_toplevel *) id; } /** * @ingroup iface_xdg_surface * * This creates an xdg_popup object for the given xdg_surface and gives * the associated wl_surface the xdg_popup role. * * If null is passed as a parent, a parent surface must be specified using * some other protocol, before committing the initial state. * * See the documentation of xdg_popup for more details about what an * xdg_popup is and how it is used. */ static inline struct xdg_popup * xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) { struct wl_proxy *id; id = wl_proxy_marshal_constructor((struct wl_proxy *) xdg_surface, XDG_SURFACE_GET_POPUP, &xdg_popup_interface, NULL, parent, positioner); return (struct xdg_popup *) id; } /** * @ingroup iface_xdg_surface * * The window geometry of a surface is its "visible bounds" from the * user's perspective. Client-side decorations often have invisible * portions like drop-shadows which should be ignored for the * purposes of aligning, placing and constraining windows. * * The window geometry is double buffered, and will be applied at the * time wl_surface.commit of the corresponding wl_surface is called. * * When maintaining a position, the compositor should treat the (x, y) * coordinate of the window geometry as the top left corner of the window. * A client changing the (x, y) window geometry coordinate should in * general not alter the position of the window. * * Once the window geometry of the surface is set, it is not possible to * unset it, and it will remain the same until set_window_geometry is * called again, even if a new subsurface or buffer is attached. * * If never set, the value is the full bounds of the surface, * including any subsurfaces. This updates dynamically on every * commit. This unset is meant for extremely simple clients. * * The arguments are given in the surface-local coordinate space of * the wl_surface associated with this xdg_surface. * * The width and height must be greater than zero. Setting an invalid size * will raise an error. When applied, the effective window geometry will be * the set window geometry clamped to the bounding rectangle of the * combined geometry of the surface of the xdg_surface and the associated * subsurfaces. */ static inline void xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_SET_WINDOW_GEOMETRY, x, y, width, height); } /** * @ingroup iface_xdg_surface * * When a configure event is received, if a client commits the * surface in response to the configure event, then the client * must make an ack_configure request sometime before the commit * request, passing along the serial of the configure event. * * For instance, for toplevel surfaces the compositor might use this * information to move a surface to the top left only when the client has * drawn itself for the maximized or fullscreen state. * * If the client receives multiple configure events before it * can respond to one, it only has to ack the last configure event. * * A client is not required to commit immediately after sending * an ack_configure request - it may even ack_configure several times * before its next surface commit. * * A client may send multiple ack_configure requests before committing, but * only the last request sent before a commit indicates which configure * event the client really is responding to. */ static inline void xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_surface, XDG_SURFACE_ACK_CONFIGURE, serial); } #ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM #define XDG_TOPLEVEL_RESIZE_EDGE_ENUM /** * @ingroup iface_xdg_toplevel * edge values for resizing * * These values are used to indicate which edge of a surface * is being dragged in a resize operation. */ enum xdg_toplevel_resize_edge { XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, }; #endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ #ifndef XDG_TOPLEVEL_STATE_ENUM #define XDG_TOPLEVEL_STATE_ENUM /** * @ingroup iface_xdg_toplevel * the surface is tiled * * The window is currently in a tiled layout and the bottom edge is * considered to be adjacent to another part of the tiling grid. */ enum xdg_toplevel_state { /** * the surface is maximized */ XDG_TOPLEVEL_STATE_MAXIMIZED = 1, /** * the surface is fullscreen */ XDG_TOPLEVEL_STATE_FULLSCREEN = 2, /** * the surface is being resized */ XDG_TOPLEVEL_STATE_RESIZING = 3, /** * the surface is now activated */ XDG_TOPLEVEL_STATE_ACTIVATED = 4, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_LEFT = 5, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_TOP = 7, /** * @since 2 */ XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, }; /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 #endif /* XDG_TOPLEVEL_STATE_ENUM */ /** * @ingroup iface_xdg_toplevel * @struct xdg_toplevel_listener */ struct xdg_toplevel_listener { /** * suggest a surface change * * This configure event asks the client to resize its toplevel * surface or to change its state. The configured state should not * be applied immediately. See xdg_surface.configure for details. * * The width and height arguments specify a hint to the window * about how its surface should be resized in window geometry * coordinates. See set_window_geometry. * * If the width or height arguments are zero, it means the client * should decide its own window dimension. This may happen when the * compositor needs to configure the state of the surface but * doesn't have any information about any previous or expected * dimension. * * The states listed in the event specify how the width/height * arguments should be interpreted, and possibly how it should be * drawn. * * Clients must send an ack_configure in response to this event. * See xdg_surface.configure and xdg_surface.ack_configure for * details. */ void (*configure)(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states); /** * surface wants to be closed * * The close event is sent by the compositor when the user wants * the surface to be closed. This should be equivalent to the user * clicking the close button in client-side decorations, if your * application has any. * * This is only a request that the user intends to close the * window. The client may choose to ignore this request, or show a * dialog to ask the user to save their data, etc. */ void (*close)(void *data, struct xdg_toplevel *xdg_toplevel); }; /** * @ingroup iface_xdg_toplevel */ static inline int xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, const struct xdg_toplevel_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, (void (**)(void)) listener, data); } #define XDG_TOPLEVEL_DESTROY 0 #define XDG_TOPLEVEL_SET_PARENT 1 #define XDG_TOPLEVEL_SET_TITLE 2 #define XDG_TOPLEVEL_SET_APP_ID 3 #define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 #define XDG_TOPLEVEL_MOVE 5 #define XDG_TOPLEVEL_RESIZE 6 #define XDG_TOPLEVEL_SET_MAX_SIZE 7 #define XDG_TOPLEVEL_SET_MIN_SIZE 8 #define XDG_TOPLEVEL_SET_MAXIMIZED 9 #define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 #define XDG_TOPLEVEL_SET_FULLSCREEN 11 #define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 #define XDG_TOPLEVEL_SET_MINIMIZED 13 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 /** * @ingroup iface_xdg_toplevel */ #define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 /** @ingroup iface_xdg_toplevel */ static inline void xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); } /** @ingroup iface_xdg_toplevel */ static inline void * xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); } static inline uint32_t xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) { return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); } /** * @ingroup iface_xdg_toplevel * * This request destroys the role surface and unmaps the surface; * see "Unmapping" behavior in interface section for details. */ static inline void xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_toplevel); } /** * @ingroup iface_xdg_toplevel * * Set the "parent" of this surface. This surface should be stacked * above the parent surface and all other ancestor surfaces. * * Parent windows should be set on dialogs, toolboxes, or other * "auxiliary" surfaces, so that the parent is raised when the dialog * is raised. * * Setting a null parent for a child window removes any parent-child * relationship for the child. Setting a null parent for a window which * currently has no parent is a no-op. * * If the parent is unmapped then its children are managed as * though the parent of the now-unmapped parent has become the * parent of this surface. If no parent exists for the now-unmapped * parent then the children are managed as though they have no * parent surface. */ static inline void xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_PARENT, parent); } /** * @ingroup iface_xdg_toplevel * * Set a short title for the surface. * * This string may be used to identify the surface in a task bar, * window list, or other user interface elements provided by the * compositor. * * The string must be encoded in UTF-8. */ static inline void xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_TITLE, title); } /** * @ingroup iface_xdg_toplevel * * Set an application identifier for the surface. * * The app ID identifies the general class of applications to which * the surface belongs. The compositor can use this to group multiple * surfaces together, or to determine how to launch a new application. * * For D-Bus activatable applications, the app ID is used as the D-Bus * service name. * * The compositor shell will try to group application surfaces together * by their app ID. As a best practice, it is suggested to select app * ID's that match the basename of the application's .desktop file. * For example, "org.freedesktop.FooViewer" where the .desktop file is * "org.freedesktop.FooViewer.desktop". * * See the desktop-entry specification [0] for more details on * application identifiers and how they relate to well-known D-Bus * names and .desktop files. * * [0] http://standards.freedesktop.org/desktop-entry-spec/ */ static inline void xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_APP_ID, app_id); } /** * @ingroup iface_xdg_toplevel * * Clients implementing client-side decorations might want to show * a context menu when right-clicking on the decorations, giving the * user a menu that they can use to maximize or minimize the window. * * This request asks the compositor to pop up such a window menu at * the given position, relative to the local surface coordinates of * the parent surface. There are no guarantees as to what menu items * the window menu contains. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. */ static inline void xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SHOW_WINDOW_MENU, seat, serial, x, y); } /** * @ingroup iface_xdg_toplevel * * Start an interactive, user-driven move of the surface. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. The passed * serial is used to determine the type of interactive move (touch, * pointer, etc). * * The server may ignore move requests depending on the state of * the surface (e.g. fullscreen or maximized), or if the passed serial * is no longer valid. * * If triggered, the surface will lose the focus of the device * (wl_pointer, wl_touch, etc) used for the move. It is up to the * compositor to visually indicate that the move is taking place, such as * updating a pointer cursor, during the move. There is no guarantee * that the device focus will return when the move is completed. */ static inline void xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_MOVE, seat, serial); } /** * @ingroup iface_xdg_toplevel * * Start a user-driven, interactive resize of the surface. * * This request must be used in response to some sort of user action * like a button press, key press, or touch down event. The passed * serial is used to determine the type of interactive resize (touch, * pointer, etc). * * The server may ignore resize requests depending on the state of * the surface (e.g. fullscreen or maximized). * * If triggered, the client will receive configure events with the * "resize" state enum value and the expected sizes. See the "resize" * enum value for more details about what is required. The client * must also acknowledge configure events using "ack_configure". After * the resize is completed, the client will receive another "configure" * event without the resize state. * * If triggered, the surface also will lose the focus of the device * (wl_pointer, wl_touch, etc) used for the resize. It is up to the * compositor to visually indicate that the resize is taking place, * such as updating a pointer cursor, during the resize. There is no * guarantee that the device focus will return when the resize is * completed. * * The edges parameter specifies how the surface should be resized, * and is one of the values of the resize_edge enum. The compositor * may use this information to update the surface position for * example when dragging the top left corner. The compositor may also * use this information to adapt its behavior, e.g. choose an * appropriate cursor image. */ static inline void xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_RESIZE, seat, serial, edges); } /** * @ingroup iface_xdg_toplevel * * Set a maximum size for the window. * * The client can specify a maximum size so that the compositor does * not try to configure the window beyond this size. * * The width and height arguments are in window geometry coordinates. * See xdg_surface.set_window_geometry. * * Values set in this way are double-buffered. They will get applied * on the next commit. * * The compositor can use this information to allow or disallow * different states like maximize or fullscreen and draw accurate * animations. * * Similarly, a tiling window manager may use this information to * place and resize client windows in a more effective way. * * The client should not rely on the compositor to obey the maximum * size. The compositor may decide to ignore the values set by the * client and request a larger size. * * If never set, or a value of zero in the request, means that the * client has no expected maximum size in the given dimension. * As a result, a client wishing to reset the maximum size * to an unspecified state can use zero for width and height in the * request. * * Requesting a maximum size to be smaller than the minimum size of * a surface is illegal and will result in a protocol error. * * The width and height must be greater than or equal to zero. Using * strictly negative values for width and height will result in a * protocol error. */ static inline void xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MAX_SIZE, width, height); } /** * @ingroup iface_xdg_toplevel * * Set a minimum size for the window. * * The client can specify a minimum size so that the compositor does * not try to configure the window below this size. * * The width and height arguments are in window geometry coordinates. * See xdg_surface.set_window_geometry. * * Values set in this way are double-buffered. They will get applied * on the next commit. * * The compositor can use this information to allow or disallow * different states like maximize or fullscreen and draw accurate * animations. * * Similarly, a tiling window manager may use this information to * place and resize client windows in a more effective way. * * The client should not rely on the compositor to obey the minimum * size. The compositor may decide to ignore the values set by the * client and request a smaller size. * * If never set, or a value of zero in the request, means that the * client has no expected minimum size in the given dimension. * As a result, a client wishing to reset the minimum size * to an unspecified state can use zero for width and height in the * request. * * Requesting a minimum size to be larger than the maximum size of * a surface is illegal and will result in a protocol error. * * The width and height must be greater than or equal to zero. Using * strictly negative values for width and height will result in a * protocol error. */ static inline void xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MIN_SIZE, width, height); } /** * @ingroup iface_xdg_toplevel * * Maximize the surface. * * After requesting that the surface should be maximized, the compositor * will respond by emitting a configure event. Whether this configure * actually sets the window maximized is subject to compositor policies. * The client must then update its content, drawing in the configured * state. The client must also acknowledge the configure when committing * the new content (see ack_configure). * * It is up to the compositor to decide how and where to maximize the * surface, for example which output and what region of the screen should * be used. * * If the surface was already maximized, the compositor will still emit * a configure event with the "maximized" state. * * If the surface is in a fullscreen state, this request has no direct * effect. It may alter the state the surface is returned to when * unmaximized unless overridden by the compositor. */ static inline void xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MAXIMIZED); } /** * @ingroup iface_xdg_toplevel * * Unmaximize the surface. * * After requesting that the surface should be unmaximized, the compositor * will respond by emitting a configure event. Whether this actually * un-maximizes the window is subject to compositor policies. * If available and applicable, the compositor will include the window * geometry dimensions the window had prior to being maximized in the * configure event. The client must then update its content, drawing it in * the configured state. The client must also acknowledge the configure * when committing the new content (see ack_configure). * * It is up to the compositor to position the surface after it was * unmaximized; usually the position the surface had before maximizing, if * applicable. * * If the surface was already not maximized, the compositor will still * emit a configure event without the "maximized" state. * * If the surface is in a fullscreen state, this request has no direct * effect. It may alter the state the surface is returned to when * unmaximized unless overridden by the compositor. */ static inline void xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_UNSET_MAXIMIZED); } /** * @ingroup iface_xdg_toplevel * * Make the surface fullscreen. * * After requesting that the surface should be fullscreened, the * compositor will respond by emitting a configure event. Whether the * client is actually put into a fullscreen state is subject to compositor * policies. The client must also acknowledge the configure when * committing the new content (see ack_configure). * * The output passed by the request indicates the client's preference as * to which display it should be set fullscreen on. If this value is NULL, * it's up to the compositor to choose which display will be used to map * this surface. * * If the surface doesn't cover the whole output, the compositor will * position the surface in the center of the output and compensate with * with border fill covering the rest of the output. The content of the * border fill is undefined, but should be assumed to be in some way that * attempts to blend into the surrounding area (e.g. solid black). * * If the fullscreened surface is not opaque, the compositor must make * sure that other screen content not part of the same surface tree (made * up of subsurfaces, popups or similarly coupled surfaces) are not * visible below the fullscreened surface. */ static inline void xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_FULLSCREEN, output); } /** * @ingroup iface_xdg_toplevel * * Make the surface no longer fullscreen. * * After requesting that the surface should be unfullscreened, the * compositor will respond by emitting a configure event. * Whether this actually removes the fullscreen state of the client is * subject to compositor policies. * * Making a surface unfullscreen sets states for the surface based on the following: * * the state(s) it may have had before becoming fullscreen * * any state(s) decided by the compositor * * any state(s) requested by the client while the surface was fullscreen * * The compositor may include the previous window geometry dimensions in * the configure event, if applicable. * * The client must also acknowledge the configure when committing the new * content (see ack_configure). */ static inline void xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_UNSET_FULLSCREEN); } /** * @ingroup iface_xdg_toplevel * * Request that the compositor minimize your surface. There is no * way to know if the surface is currently minimized, nor is there * any way to unset minimization on this surface. * * If you are looking to throttle redrawing when minimized, please * instead use the wl_surface.frame event for this, as this will * also work with live previews on windows in Alt-Tab, Expose or * similar compositor features. */ static inline void xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) { wl_proxy_marshal((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SET_MINIMIZED); } #ifndef XDG_POPUP_ERROR_ENUM #define XDG_POPUP_ERROR_ENUM enum xdg_popup_error { /** * tried to grab after being mapped */ XDG_POPUP_ERROR_INVALID_GRAB = 0, }; #endif /* XDG_POPUP_ERROR_ENUM */ /** * @ingroup iface_xdg_popup * @struct xdg_popup_listener */ struct xdg_popup_listener { /** * configure the popup surface * * This event asks the popup surface to configure itself given * the configuration. The configured state should not be applied * immediately. See xdg_surface.configure for details. * * The x and y arguments represent the position the popup was * placed at given the xdg_positioner rule, relative to the upper * left corner of the window geometry of the parent surface. * @param x x position relative to parent surface window geometry * @param y y position relative to parent surface window geometry * @param width window geometry width * @param height window geometry height */ void (*configure)(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height); /** * popup interaction is done * * The popup_done event is sent out when a popup is dismissed by * the compositor. The client should destroy the xdg_popup object * at this point. */ void (*popup_done)(void *data, struct xdg_popup *xdg_popup); }; /** * @ingroup iface_xdg_popup */ static inline int xdg_popup_add_listener(struct xdg_popup *xdg_popup, const struct xdg_popup_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, (void (**)(void)) listener, data); } #define XDG_POPUP_DESTROY 0 #define XDG_POPUP_GRAB 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_DESTROY_SINCE_VERSION 1 /** * @ingroup iface_xdg_popup */ #define XDG_POPUP_GRAB_SINCE_VERSION 1 /** @ingroup iface_xdg_popup */ static inline void xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) { wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); } /** @ingroup iface_xdg_popup */ static inline void * xdg_popup_get_user_data(struct xdg_popup *xdg_popup) { return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); } static inline uint32_t xdg_popup_get_version(struct xdg_popup *xdg_popup) { return wl_proxy_get_version((struct wl_proxy *) xdg_popup); } /** * @ingroup iface_xdg_popup * * This destroys the popup. Explicitly destroying the xdg_popup * object will also dismiss the popup, and unmap the surface. * * If this xdg_popup is not the "topmost" popup, a protocol error * will be sent. */ static inline void xdg_popup_destroy(struct xdg_popup *xdg_popup) { wl_proxy_marshal((struct wl_proxy *) xdg_popup, XDG_POPUP_DESTROY); wl_proxy_destroy((struct wl_proxy *) xdg_popup); } /** * @ingroup iface_xdg_popup * * This request makes the created popup take an explicit grab. An explicit * grab will be dismissed when the user dismisses the popup, or when the * client destroys the xdg_popup. This can be done by the user clicking * outside the surface, using the keyboard, or even locking the screen * through closing the lid or a timeout. * * If the compositor denies the grab, the popup will be immediately * dismissed. * * This request must be used in response to some sort of user action like a * button press, key press, or touch down event. The serial number of the * event should be passed as 'serial'. * * The parent of a grabbing popup must either be an xdg_toplevel surface or * another xdg_popup with an explicit grab. If the parent is another * xdg_popup it means that the popups are nested, with this popup now being * the topmost popup. * * Nested popups must be destroyed in the reverse order they were created * in, e.g. the only popup you are allowed to destroy at all times is the * topmost one. * * When compositors choose to dismiss a popup, they may dismiss every * nested grabbing popup as well. When a compositor dismisses popups, it * will follow the same dismissing order as required from the client. * * The parent of a grabbing popup must either be another xdg_popup with an * active explicit grab, or an xdg_popup or xdg_toplevel, if there are no * explicit grabs already taken. * * If the topmost grabbing popup is destroyed, the grab will be returned to * the parent of the popup, if that parent previously had an explicit grab. * * If the parent is a grabbing popup which has already been dismissed, this * popup will be immediately dismissed. If the parent is a popup that did * not take an explicit grab, an error will be raised. * * During a popup grab, the client owning the grab will receive pointer * and touch events for all their surfaces as normal (similar to an * "owner-events" grab in X11 parlance), while the top most grabbing popup * will always have keyboard focus. */ static inline void xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) { wl_proxy_marshal((struct wl_proxy *) xdg_popup, XDG_POPUP_GRAB, seat, serial); } #ifdef __cplusplus } #endif #endif ================================================ FILE: src/Wsi.h ================================================ #ifndef HELLO_TRIANGLE_WSI_PLATFORM_H #define HELLO_TRIANGLE_WSI_PLATFORM_H #if !defined(USE_PLATFORM_GLFW) \ && !defined(VK_USE_PLATFORM_ANDROID_KHR) \ && !defined(VK_USE_PLATFORM_WAYLAND_KHR) \ && !defined(VK_USE_PLATFORM_WIN32_KHR) \ && !defined(VK_USE_PLATFORM_XCB_KHR) \ && !defined(VK_USE_PLATFORM_XLIB_KHR) \ && !defined(VK_USE_PLATFORM_IOS_MVK) \ && !defined(VK_USE_PLATFORM_MACOS_MVK) \ && !defined(VK_USE_PLATFORM_VI_NN) #error "Exactly one Vulkan WSI platform must be defined." #endif #if defined(USE_PLATFORM_GLFW) #include "WSI/Glfw.h" #elif defined(VK_USE_PLATFORM_WIN32_KHR) #include "WSI/Win32.h" #elif defined(VK_USE_PLATFORM_XLIB_KHR) #include "WSI/Xlib.h" #elif defined(VK_USE_PLATFORM_XCB_KHR) #include "WSI/Xcb.h" #elif defined(VK_USE_PLATFORM_WAYLAND_KHR) #include "WSI/Wayland.h" #else #error "Unsupported Vulkan WSI platform, or none selected." #endif #ifndef VK_USE_PLATFORM_WAYLAND_KHR // dummy impl for platforms that do not need these functions uint32_t getWindowWidth( PlatformWindow ){ return 0; } uint32_t getWindowHeight( PlatformWindow ){ return 0; } #endif #endif //HELLO_TRIANGLE_WSI_PLATFORM_H ================================================ FILE: src/shaders/hello_triangle.frag ================================================ #version 450 layout (location = 0) smooth in vec3 inColor; layout (location = 0) out vec4 outFragColor; void main(){ outFragColor = vec4( inColor, 1.0 ); } ================================================ FILE: src/shaders/hello_triangle.vert ================================================ #version 450 layout (location = 0) in vec2 inPos; layout (location = 1) in vec3 inColor; layout (location = 0) smooth out vec3 outColor; void main(){ outColor = inColor; gl_Position = vec4( inPos.xy, 0.0, 1.0 ); }