From 7ec394db8961009e6ac23fea909f8353d865f7a3 Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Fri, 9 May 2025 00:32:33 +0200 Subject: Add and use Vulkan synchronization library --- .clang-tidy | 1 + MODULE.bazel | 2 +- MODULE.bazel.lock | 72 +++++++----- hk21/game/BUILD.bazel | 1 + hk21/game/gpu.cpp | 170 +++++++++++++++------------ hk21/vulkan/BUILD.bazel | 13 --- hk21/vulkan/loader/BUILD.bazel | 2 +- hk21/vulkan/loader/loader.hpp | 3 +- hk21/vulkan/sync/BUILD.bazel | 33 ++++++ hk21/vulkan/sync/sync.cpp | 252 ++++++++++++++++++++++++++++++++++++++++ hk21/vulkan/sync/sync.hpp | 64 ++++++++++ hk21/vulkan/sync/sync_tests.cpp | 162 ++++++++++++++++++++++++++ hk21/vulkan/vulkan.hpp | 12 -- vendor/vulkan/BUILD.bazel | 5 + 14 files changed, 658 insertions(+), 134 deletions(-) create mode 100644 hk21/vulkan/sync/BUILD.bazel create mode 100644 hk21/vulkan/sync/sync.cpp create mode 100644 hk21/vulkan/sync/sync.hpp create mode 100644 hk21/vulkan/sync/sync_tests.cpp delete mode 100644 hk21/vulkan/vulkan.hpp diff --git a/.clang-tidy b/.clang-tidy index d7337bd..84f54ef 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -32,3 +32,4 @@ Checks: - "-readability-function-cognitive-complexity" - "-readability-math-missing-parentheses" - "-*-rvalue-reference-param-not-moved" + - "-*-enum-size" diff --git a/MODULE.bazel b/MODULE.bazel index ffad85c..46b251c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,4 +19,4 @@ git_override( ) bazel_dep(name = "sdl3_windows", version = "3.2.6") -bazel_dep(name = "asl", version = "0.3.0") +bazel_dep(name = "asl", version = "0.4.0") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 28c0ad1..8bb3b0a 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -10,7 +10,7 @@ "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", - "https://bcr.bazel.build/modules/asl/0.3.0/MODULE.bazel": "not found", + "https://bcr.bazel.build/modules/asl/0.4.0/MODULE.bazel": "not found", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", @@ -91,10 +91,10 @@ "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", + "https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", - "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", - "https://bcr.bazel.build/modules/rules_java/8.6.1/source.json": "f18d9ad3c4c54945bf422ad584fa6c5ca5b3116ff55a5b1bc77e5c1210be5960", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", @@ -124,8 +124,8 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", - "https://bcr.bazel.build/modules/rules_python/1.1.0/MODULE.bazel": "57e01abae22956eb96d891572490d20e07d983e0c065de0b2170cafe5053e788", - "https://bcr.bazel.build/modules/rules_python/1.1.0/source.json": "29f1fdfd23a40808c622f813bc93e29c3aae277333f03293f667e76159750a0f", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", "https://bcr.bazel.build/modules/sdl3_windows/3.2.6/MODULE.bazel": "not found", @@ -142,35 +142,13 @@ "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", "https://git.stevenlr.com/460nm/bazel-registry.git/plain/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", - "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/asl/0.3.0/MODULE.bazel": "8960caf858f64438a29a505584b19ede9dab4b5c6270b6c9e8abe28eab3d6abe", - "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/asl/0.3.0/source.json": "e315934f0b2831e71a91efe6af3b6ccc67faea086a006e1fff29d56e25b55db7", + "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/asl/0.4.0/MODULE.bazel": "767046b78b3dfeec294ab47800d20d2c3220f6d29aca915fef12868305d4b7eb", + "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/asl/0.4.0/source.json": "b789c6c9dbbd5f8b256e47b31ff4869d23c6c2a31e7ed54acb6b4898252814dd", "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/sdl3_windows/3.2.6/MODULE.bazel": "857507d99ce37f1a4cb331a7888cec8511f38b0bfd46adb55d1b70a9cbe4e86f", "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/sdl3_windows/3.2.6/source.json": "5196961bbdfc230463ce7ea9049f7bab62b5ea7bccbfd376b85d07b2f909f98b" }, "selectedYankedVersions": {}, "moduleExtensions": { - "@@rules_java+//java:rules_java_deps.bzl%compatibility_proxy": { - "general": { - "bzlTransitiveDigest": "84xJEZ1jnXXwo8BXMprvBm++rRt4jsTu9liBxz0ivps=", - "usagesDigest": "jTQDdLDxsS43zuRmg1faAjIEPWdLAbDAowI1pInQSoo=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "compatibility_proxy": { - "repoRuleId": "@@rules_java+//java:rules_java_deps.bzl%_compatibility_proxy_repo_rule", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_java+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", @@ -234,6 +212,42 @@ ] ] } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=", + "usagesDigest": "vJ5RHUxAnV24M5swNGiAnkdxMx3Hp/iOLmNANTC5Xc8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python+", + "platforms", + "platforms" + ] + ] + } } } } diff --git a/hk21/game/BUILD.bazel b/hk21/game/BUILD.bazel index 0a571f3..218d1f0 100644 --- a/hk21/game/BUILD.bazel +++ b/hk21/game/BUILD.bazel @@ -18,6 +18,7 @@ cc_binary( "@asl//asl/containers:buffer", "@sdl3_windows//:sdl3", "//hk21/vulkan/loader", + "//hk21/vulkan/sync", ], applicable_licenses = ["//:license"], ) diff --git a/hk21/game/gpu.cpp b/hk21/game/gpu.cpp index 0d74559..a929e1a 100644 --- a/hk21/game/gpu.cpp +++ b/hk21/game/gpu.cpp @@ -14,11 +14,11 @@ #include #include "hk21/vulkan/loader/loader.hpp" +#include "hk21/vulkan/sync/sync.hpp" // @Todo Make fences recyclable // @Todo Make command pool recyclable // @Todo Make frame structure recyclable -// @Todo Auto barriers for images #define VK_ALLOCATOR nullptr @@ -288,8 +288,53 @@ static asl::status_or create_device(VkPhysicalDevice physical_device, struct FrameResources : asl::intrusive_list_node { - VkFence complete_fence = VK_NULL_HANDLE; - VkCommandPool command_pool = VK_NULL_HANDLE; + VkFence complete_fence = VK_NULL_HANDLE; + VkCommandPool command_pool = VK_NULL_HANDLE; +}; + +class DependencyInfoBuilder : public vulkan_sync::DependencyInfoBuilder +{ + // @Todo Configure allocator + + asl::buffer m_image_barriers; + asl::buffer m_buffer_barriers; + +public: + void add_image_barrier(const VkImageMemoryBarrier2& barrier) override + { + m_image_barriers.push(barrier); + } + + void add_buffer_barrier(const VkBufferMemoryBarrier2& barrier) override + { + m_buffer_barriers.push(barrier); + } + + void apply(VkCommandBuffer command_buffer) + { + if (m_image_barriers.is_empty() && + m_buffer_barriers.is_empty()) + { + return; + } + + VkDependencyInfo dependency_info{ + .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, + .pNext = nullptr, + .dependencyFlags = 0, + .memoryBarrierCount = 0, + .pMemoryBarriers = nullptr, + .bufferMemoryBarrierCount = static_cast(m_buffer_barriers.size()), + .pBufferMemoryBarriers = m_buffer_barriers.data(), + .imageMemoryBarrierCount = static_cast(m_image_barriers.size()), + .pImageMemoryBarriers = m_image_barriers.data(), + }; + + vkCmdPipelineBarrier2(command_buffer, &dependency_info); + + m_image_barriers.clear(); + m_buffer_barriers.clear(); + } }; class GpuImpl : public Gpu @@ -303,8 +348,14 @@ class GpuImpl : public Gpu VkDevice m_device; VkQueue m_queue; + struct Image + { + vulkan_sync::ImageState state; + VkImage image{}; + }; + asl::option m_swapchain; - asl::buffer m_swapchain_images; + asl::buffer m_swapchain_images; VkSemaphore m_swapchain_image_acquire_semaphore = VK_NULL_HANDLE; VkSemaphore m_queue_complete_semaphore = VK_NULL_HANDLE; @@ -312,6 +363,8 @@ class GpuImpl : public Gpu asl::GlobalHeap m_allocator; // @Todo Make this configurable asl::IntrusiveList m_in_flight_frames; + DependencyInfoBuilder m_dependency_builder; + public: GpuImpl( VkInstance instance, @@ -451,11 +504,24 @@ public: m_swapchain = swapchain; vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, nullptr); - m_swapchain_images.resize_zero(count); - res = vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, m_swapchain_images.data()); - if (res != VK_SUCCESS) + { - return asl::runtime_error("Couldn't retrieve Vulkan swapchain images: {}", res); + m_swapchain_images.resize(count); + + // @Todo Good candidate for temporary allocation + asl::buffer images; + images.resize_zero(count); + + res = vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, images.data()); + if (res != VK_SUCCESS) + { + return asl::runtime_error("Couldn't retrieve Vulkan swapchain images: {}", res); + } + + for (int64_t i = 0; i < count; ++i) + { + m_swapchain_images[i].image = images[i]; + } } ASL_LOG_INFO("Vulkan swapchain created ({}x{} with {} images)", @@ -518,6 +584,8 @@ public: { return asl::runtime_error("Couldn't acquire swapchain image: {}", res); } + + auto& swapchain_image = m_swapchain_images[image_index]; VkCommandPoolCreateInfo command_pool_create_info{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, @@ -561,40 +629,12 @@ public: return asl::runtime_error("Couldn't begin command buffer: {}", res); } - VkImageMemoryBarrier2 barrier1{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .pNext = nullptr, - .srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, - .srcAccessMask = VK_ACCESS_2_NONE, - .dstStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT, - .dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .newLayout = VK_IMAGE_LAYOUT_GENERAL, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = m_swapchain_images[image_index], - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; - - VkDependencyInfo dependency_info1{ - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .pNext = nullptr, - .dependencyFlags = 0, - .memoryBarrierCount = 0, - .pMemoryBarriers = nullptr, - .bufferMemoryBarrierCount = 0, - .pBufferMemoryBarriers = nullptr, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier1, - }; - - vkCmdPipelineBarrier2(command_buffer, &dependency_info1); + vulkan_sync::synchronize_resource( + swapchain_image.image, + VK_IMAGE_ASPECT_COLOR_BIT, + &swapchain_image.state, + vulkan_sync::Usage::kImageClear, + &m_dependency_builder); VkClearColorValue clear_color{ .float32 = { 0.0F, 0.137F, 0.4F, 1.0F }, @@ -603,47 +643,23 @@ public: VkImageSubresourceRange range{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, - .levelCount = 1, + .levelCount = VK_REMAINING_MIP_LEVELS, .baseArrayLayer = 0, - .layerCount = 1, + .layerCount = VK_REMAINING_ARRAY_LAYERS, }; - vkCmdClearColorImage(command_buffer, m_swapchain_images[image_index], VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); + m_dependency_builder.apply(command_buffer); + vkCmdClearColorImage(command_buffer, swapchain_image.image, swapchain_image.state.current_layout, &clear_color, 1, &range); - VkImageMemoryBarrier2 barrier2{ - .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, - .pNext = nullptr, - .srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT, - .srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, - .dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, - .dstAccessMask = VK_ACCESS_2_NONE, - .oldLayout = VK_IMAGE_LAYOUT_GENERAL, - .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = m_swapchain_images[image_index], - .subresourceRange = { - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }; + vulkan_sync::synchronize_resource( + swapchain_image.image, + VK_IMAGE_ASPECT_COLOR_BIT, + &swapchain_image.state, + vulkan_sync::Usage::kImagePresent, + &m_dependency_builder); - VkDependencyInfo dependency_info2{ - .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, - .pNext = nullptr, - .dependencyFlags = 0, - .memoryBarrierCount = 0, - .pMemoryBarriers = nullptr, - .bufferMemoryBarrierCount = 0, - .pBufferMemoryBarriers = nullptr, - .imageMemoryBarrierCount = 1, - .pImageMemoryBarriers = &barrier2, - }; + m_dependency_builder.apply(command_buffer); - vkCmdPipelineBarrier2(command_buffer, &dependency_info2); res = vkEndCommandBuffer(command_buffer); if (res != VK_SUCCESS) diff --git a/hk21/vulkan/BUILD.bazel b/hk21/vulkan/BUILD.bazel index 25cc0d6..3bbac5a 100644 --- a/hk21/vulkan/BUILD.bazel +++ b/hk21/vulkan/BUILD.bazel @@ -1,16 +1,3 @@ # Copyright 2025 Steven Le Rouzic # # SPDX-License-Identifier: BSD-3-Clause - -cc_library( - name = "vulkan", - hdrs = [ - "vulkan.hpp", - ], - deps = [ - "//vendor/vulkan", - "@asl//asl/base", - ], - visibility = ["//:__subpackages__"], - applicable_licenses = ["//:license"], -) diff --git a/hk21/vulkan/loader/BUILD.bazel b/hk21/vulkan/loader/BUILD.bazel index 32e8f03..db49ee6 100644 --- a/hk21/vulkan/loader/BUILD.bazel +++ b/hk21/vulkan/loader/BUILD.bazel @@ -12,7 +12,7 @@ cc_library( "fns.hpp", ], deps = [ - "//hk21/vulkan", + "//vendor/vulkan", "@asl//asl/base", "@asl//asl/types:status", ], diff --git a/hk21/vulkan/loader/loader.hpp b/hk21/vulkan/loader/loader.hpp index e1147f4..3461c99 100644 --- a/hk21/vulkan/loader/loader.hpp +++ b/hk21/vulkan/loader/loader.hpp @@ -7,7 +7,8 @@ #include #include -#include "hk21/vulkan/vulkan.hpp" +#include + #include "hk21/vulkan/loader/fns.hpp" #define FN(NAME) extern PFN_##NAME NAME; diff --git a/hk21/vulkan/sync/BUILD.bazel b/hk21/vulkan/sync/BUILD.bazel new file mode 100644 index 0000000..fc74cad --- /dev/null +++ b/hk21/vulkan/sync/BUILD.bazel @@ -0,0 +1,33 @@ +# Copyright 2025 Steven Le Rouzic +# +# SPDX-License-Identifier: BSD-3-Clause + +cc_library( + name = "sync", + hdrs = [ + "sync.hpp", + ], + srcs = [ + "sync.cpp", + ], + deps = [ + "//vendor/vulkan", + "@asl//asl/base", + "@asl//asl/types:option", + "@asl//asl/types:span", + ], + visibility = ["//:__subpackages__"], + applicable_licenses = ["//:license"], +) + +cc_test( + name = "tests", + srcs = [ + "sync_tests.cpp", + ], + deps = [ + ":sync", + "@asl//asl/containers:buffer", + "@asl//asl/testing", + ], +) diff --git a/hk21/vulkan/sync/sync.cpp b/hk21/vulkan/sync/sync.cpp new file mode 100644 index 0000000..1c81ce3 --- /dev/null +++ b/hk21/vulkan/sync/sync.cpp @@ -0,0 +1,252 @@ +// Copyright 2025 Steven Le Rouzic +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "hk21/vulkan/sync/sync.hpp" + +#include + +// All of this is largely inspired by nicegraf's synchronization utility. +// See https://github.com/nicebyte/nicegraf/blob/3a291433fdb4fd9cf38356f297ff1d851617f0f5/source/ngf-vk/impl.c#L2718 + +namespace +{ + +enum StageAccess : uint32_t +{ + kClearStageTransferWrite = 0x0000'0001U, + kFragmentStageShaderSampled = 0x0000'0002U, + kVertexStageShaderSampled = 0x0000'0004U, + kColorAttachmentWrite = 0x0000'0008U, +}; + +constexpr VkAccessFlags kWriteAccessMask = + VK_ACCESS_2_SHADER_WRITE_BIT + | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT + | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT + | VK_ACCESS_2_TRANSFER_WRITE_BIT + | VK_ACCESS_2_HOST_WRITE_BIT + | VK_ACCESS_2_MEMORY_WRITE_BIT + | VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT; + +struct UsageInfo +{ + uint32_t stage_access_mask{}; + VkAccessFlags2 access_flags{}; + VkPipelineStageFlags2 pipeline_stage_flags{}; + VkImageLayout image_layout{}; +}; + +const auto kUsageInfos = ([]() static { + using namespace vulkan_sync; + + static UsageInfo info[asl::to_underlying(Usage::kCount_)]{}; + + info[asl::to_underlying(Usage::kImageClear)] = UsageInfo{ + .stage_access_mask = StageAccess::kClearStageTransferWrite, + .access_flags = VK_ACCESS_2_TRANSFER_WRITE_BIT, + .pipeline_stage_flags = VK_PIPELINE_STAGE_2_CLEAR_BIT, + .image_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + }; + + info[asl::to_underlying(Usage::kImagePresent)] = UsageInfo{ + .stage_access_mask = 0, + .access_flags = VK_ACCESS_2_NONE, + .pipeline_stage_flags = VK_PIPELINE_STAGE_2_NONE, + .image_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + + info[asl::to_underlying(Usage::kImageSampledInFragmentShader)] = UsageInfo{ + .stage_access_mask = StageAccess::kFragmentStageShaderSampled, + .access_flags = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT, + .pipeline_stage_flags = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + .image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + + info[asl::to_underlying(Usage::kImageSampledInVertexShader)] = UsageInfo{ + .stage_access_mask = StageAccess::kVertexStageShaderSampled, + .access_flags = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT, + .pipeline_stage_flags = VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT, + .image_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }; + + info[asl::to_underlying(Usage::kImageColorWriteAttachment)] = UsageInfo{ + .stage_access_mask = StageAccess::kColorAttachmentWrite, + .access_flags = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + .pipeline_stage_flags = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + .image_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + return asl::span(info); +})(); + + + +// We use an image barrier as a common structure for image and buffer barriers. +// The only differences are the resource, the subresource range, and the image layouts. +// We just discard whatever we don't need and fill the more specific fields outside. +asl::option synchronize_resource_( + vulkan_sync::ResourceState* state, + VkImageLayout* state_layout, + vulkan_sync::Usage new_usage) +{ + const UsageInfo& usage_info = kUsageInfos[asl::to_underlying(new_usage)]; + + const bool is_read_only_access = (usage_info.access_flags & kWriteAccessMask) == 0U; + + const bool needs_layout_transition = *state_layout != usage_info.image_layout; + + const bool needs_write = needs_layout_transition || !is_read_only_access; + + VkImageMemoryBarrier2 barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = 0, + .srcAccessMask = 0, + .dstStageMask = 0, + .dstAccessMask = 0, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = VK_NULL_HANDLE, + .subresourceRange = {}, + }; + + if (needs_write) + { + barrier.srcStageMask |= asl::exchange(state->active_readers_pipeline_stage_mask, VK_PIPELINE_STAGE_2_NONE); + barrier.srcAccessMask |= asl::exchange(state->active_readers_access_mask, VK_ACCESS_2_NONE); + state->has_seen_last_write = 0; + + // If there was to read since last write, but there was a write, + // synchronize with last write instead. + if (barrier.srcStageMask == VK_PIPELINE_STAGE_2_NONE && + state->last_writer_pipeline_stage_mask != VK_PIPELINE_STAGE_2_NONE) + { + barrier.srcStageMask |= state->last_writer_pipeline_stage_mask; + barrier.srcAccessMask |= state->last_writer_access_mask; + } + + // Last write is now the new usage. + state->last_writer_pipeline_stage_mask = usage_info.pipeline_stage_flags; + state->last_writer_access_mask = usage_info.access_flags; + + // If this is a read-only that is considered a write (layout transition) + // we also record the reader info, because it acts as if this read has been + // synchronized. + if (is_read_only_access) + { + state->has_seen_last_write |= usage_info.stage_access_mask; + state->active_readers_access_mask |= usage_info.access_flags; + state->active_readers_pipeline_stage_mask |= usage_info.pipeline_stage_flags; + } + } + else + { + // If there was a previous write we need to synchronize with, and this + // access has not been synchronized with it yet, synchronize. + if (state->last_writer_pipeline_stage_mask != VK_PIPELINE_STAGE_2_NONE + && (state->has_seen_last_write & usage_info.stage_access_mask) != usage_info.stage_access_mask) + { + barrier.srcStageMask |= state->last_writer_pipeline_stage_mask; + barrier.srcAccessMask |= state->last_writer_access_mask; + } + + // Record this reader info. + state->has_seen_last_write |= usage_info.stage_access_mask; + state->active_readers_access_mask |= usage_info.access_flags; + state->active_readers_pipeline_stage_mask |= usage_info.pipeline_stage_flags; + } + + // If the barrier has been filled or we need a layout transition, emit a barrier. + if (barrier.srcStageMask != VK_PIPELINE_STAGE_2_NONE || needs_layout_transition) + { + barrier.dstStageMask |= usage_info.pipeline_stage_flags; + barrier.dstAccessMask |= usage_info.access_flags; + + if (barrier.dstStageMask == VK_PIPELINE_STAGE_2_NONE) + { + barrier.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + } + + if (barrier.srcStageMask == VK_PIPELINE_STAGE_2_NONE) + { + barrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; + } + + if (needs_layout_transition) + { + barrier.oldLayout = asl::exchange(*state_layout, usage_info.image_layout); + barrier.newLayout = usage_info.image_layout; + } + + return barrier; + } + + return asl::nullopt; +} + +} // anonymous namespace + +namespace vulkan_sync +{ + +void synchronize_resource( + VkImage image, VkImageAspectFlags aspects, + ImageState* state, Usage new_usage, DependencyInfoBuilder* builder) +{ + const UsageInfo& usage_info = kUsageInfos[asl::to_underlying(new_usage)]; + ASL_ASSERT(usage_info.image_layout != VK_IMAGE_LAYOUT_UNDEFINED); + + auto barrier_opt = synchronize_resource_(state, &state->current_layout, new_usage); + if (barrier_opt.has_value()) + { + auto& barrier = barrier_opt.value(); + + barrier.image = image; + barrier.subresourceRange = { + .aspectMask = aspects, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }; + + builder->add_image_barrier(barrier); + } +} + +void synchronize_resource( + VkBuffer buffer, BufferState* state, + Usage new_usage, DependencyInfoBuilder* builder) +{ + const UsageInfo& usage_info = kUsageInfos[asl::to_underlying(new_usage)]; + ASL_ASSERT(usage_info.image_layout == VK_IMAGE_LAYOUT_UNDEFINED); + + VkImageLayout dummy_layout = VK_IMAGE_LAYOUT_UNDEFINED; + auto barrier_opt = synchronize_resource_(state, &dummy_layout, new_usage); + if (barrier_opt.has_value()) + { + const auto& image_barrier = barrier_opt.value(); + + VkBufferMemoryBarrier2 barrier{ + .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2, + .pNext = nullptr, + .srcStageMask = image_barrier.srcStageMask, + .srcAccessMask = image_barrier.srcAccessMask, + .dstStageMask = image_barrier.dstStageMask, + .dstAccessMask = image_barrier.dstAccessMask, + .srcQueueFamilyIndex = image_barrier.srcQueueFamilyIndex, + .dstQueueFamilyIndex = image_barrier.dstQueueFamilyIndex, + .buffer = buffer, + .offset = 0, + .size = VK_WHOLE_SIZE, + }; + + builder->add_buffer_barrier(barrier); + } +} + +} // namespace vulkan_sync + diff --git a/hk21/vulkan/sync/sync.hpp b/hk21/vulkan/sync/sync.hpp new file mode 100644 index 0000000..b89b625 --- /dev/null +++ b/hk21/vulkan/sync/sync.hpp @@ -0,0 +1,64 @@ +// Copyright 2025 Steven Le Rouzic +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +#include +#include +#include +#include + +namespace vulkan_sync +{ + +class DependencyInfoBuilder +{ +public: + DependencyInfoBuilder() = default; + ASL_DEFAULT_COPY_MOVE(DependencyInfoBuilder); + virtual ~DependencyInfoBuilder() = default; + + virtual void add_image_barrier(const VkImageMemoryBarrier2&) = 0; + virtual void add_buffer_barrier(const VkBufferMemoryBarrier2&) = 0; +}; + +struct ResourceState +{ + VkAccessFlags2 last_writer_access_mask{}; + VkPipelineStageFlags2 last_writer_pipeline_stage_mask{}; + + VkAccessFlags2 active_readers_access_mask{}; + VkPipelineStageFlags2 active_readers_pipeline_stage_mask{}; + + // Which StageAccess-es have seen the previous write. + uint32_t has_seen_last_write{}; +}; + +struct BufferState : public ResourceState {}; + +struct ImageState : public ResourceState +{ + VkImageLayout current_layout = VK_IMAGE_LAYOUT_UNDEFINED; +}; + +enum class Usage : uint32_t +{ + kImageClear, + kImagePresent, + kImageSampledInFragmentShader, + kImageSampledInVertexShader, + kImageColorWriteAttachment, + + kCount_, +}; + +void synchronize_resource( + VkImage, VkImageAspectFlags, + ImageState*, Usage new_usage, DependencyInfoBuilder*); + +void synchronize_resource( + VkBuffer, BufferState*, + Usage new_usage, DependencyInfoBuilder*); + +} // namespace vulkan_sync diff --git a/hk21/vulkan/sync/sync_tests.cpp b/hk21/vulkan/sync/sync_tests.cpp new file mode 100644 index 0000000..ff6bfd0 --- /dev/null +++ b/hk21/vulkan/sync/sync_tests.cpp @@ -0,0 +1,162 @@ +// Copyright 2025 Steven Le Rouzic +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "hk21/vulkan/sync/sync.hpp" + +#include +#include + +class DependencyInfoBuilder : public vulkan_sync::DependencyInfoBuilder +{ + asl::buffer m_image_barriers; + asl::buffer m_buffer_barriers; + +public: + void add_image_barrier(const VkImageMemoryBarrier2& barrier) override + { + m_image_barriers.push(barrier); + } + + void add_buffer_barrier(const VkBufferMemoryBarrier2& barrier) override + { + m_buffer_barriers.push(barrier); + } + + void reset() + { + m_image_barriers.clear(); + m_buffer_barriers.clear(); + } + + asl::span image_barriers() const + { + return m_image_barriers; + } + + asl::span buffer_barriers() const + { + return m_buffer_barriers; + } +}; + +ASL_TEST(clear_and_present) +{ + DependencyInfoBuilder builder; + vulkan_sync::ImageState state{}; + + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageClear, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + auto barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_NONE); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageClear, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImagePresent, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_NONE); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageClear, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_NONE); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +} + +ASL_TEST(clear_and_draw) +{ + DependencyInfoBuilder builder; + vulkan_sync::ImageState state{}; + + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageColorWriteAttachment, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + auto barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_NONE); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageSampledInVertexShader, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageSampledInFragmentShader, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_UNDEFINED); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageSampledInVertexShader, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 0); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + + builder.reset(); + synchronize_resource(VK_NULL_HANDLE, {}, &state, vulkan_sync::Usage::kImageClear, &builder); + ASL_TEST_ASSERT(builder.buffer_barriers().size() == 0); + ASL_TEST_ASSERT(builder.image_barriers().size() == 1); + barrier = builder.image_barriers()[0]; + ASL_TEST_EXPECT(barrier.srcStageMask == (VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT)); + ASL_TEST_EXPECT(barrier.srcAccessMask == VK_ACCESS_2_SHADER_SAMPLED_READ_BIT); + ASL_TEST_EXPECT(barrier.dstStageMask == VK_PIPELINE_STAGE_2_CLEAR_BIT); + ASL_TEST_EXPECT(barrier.dstAccessMask == VK_ACCESS_2_TRANSFER_WRITE_BIT); + ASL_TEST_EXPECT(barrier.oldLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + ASL_TEST_EXPECT(barrier.newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + ASL_TEST_EXPECT(state.current_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); +} + diff --git a/hk21/vulkan/vulkan.hpp b/hk21/vulkan/vulkan.hpp deleted file mode 100644 index 0e3242b..0000000 --- a/hk21/vulkan/vulkan.hpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2025 Steven Le Rouzic -// -// SPDX-License-Identifier: BSD-3-Clause - -#pragma once - -#include - -#define VK_NO_STDDEF_H -#define VK_NO_STDINT_H -#define VK_NO_PROTOTYPES -#include diff --git a/vendor/vulkan/BUILD.bazel b/vendor/vulkan/BUILD.bazel index 0a1a15a..6b9b662 100644 --- a/vendor/vulkan/BUILD.bazel +++ b/vendor/vulkan/BUILD.bazel @@ -21,6 +21,11 @@ cc_library( "vulkan.h", "vulkan_core.h", ], + defines = [ + "VK_NO_STDDER_H", + "VK_NO_STDINT_H", + "VK_NO_PROTOTYPES", + ], includes = [ ".", ], -- cgit