Add and use Vulkan synchronization library

This commit is contained in:
2025-05-09 00:32:33 +02:00
parent 69d33476b8
commit 7ec394db89
14 changed files with 658 additions and 134 deletions

View File

@ -32,3 +32,4 @@ Checks:
- "-readability-function-cognitive-complexity" - "-readability-function-cognitive-complexity"
- "-readability-math-missing-parentheses" - "-readability-math-missing-parentheses"
- "-*-rvalue-reference-param-not-moved" - "-*-rvalue-reference-param-not-moved"
- "-*-enum-size"

View File

@ -19,4 +19,4 @@ git_override(
) )
bazel_dep(name = "sdl3_windows", version = "3.2.6") 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")

72
MODULE.bazel.lock generated
View File

@ -10,7 +10,7 @@
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "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/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", "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.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.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "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.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.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/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.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.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/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.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "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.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.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/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.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
"https://bcr.bazel.build/modules/rules_python/1.1.0/source.json": "29f1fdfd23a40808c622f813bc93e29c3aae277333f03293f667e76159750a0f", "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/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
"https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", "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", "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.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", "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/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.4.0/MODULE.bazel": "767046b78b3dfeec294ab47800d20d2c3220f6d29aca915fef12868305d4b7eb",
"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/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/MODULE.bazel": "857507d99ce37f1a4cb331a7888cec8511f38b0bfd46adb55d1b70a9cbe4e86f",
"https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/sdl3_windows/3.2.6/source.json": "5196961bbdfc230463ce7ea9049f7bab62b5ea7bccbfd376b85d07b2f909f98b" "https://git.stevenlr.com/460nm/bazel-registry.git/plain/modules/sdl3_windows/3.2.6/source.json": "5196961bbdfc230463ce7ea9049f7bab62b5ea7bccbfd376b85d07b2f909f98b"
}, },
"selectedYankedVersions": {}, "selectedYankedVersions": {},
"moduleExtensions": { "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": { "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": { "general": {
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", "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"
]
]
}
} }
} }
} }

View File

@ -18,6 +18,7 @@ cc_binary(
"@asl//asl/containers:buffer", "@asl//asl/containers:buffer",
"@sdl3_windows//:sdl3", "@sdl3_windows//:sdl3",
"//hk21/vulkan/loader", "//hk21/vulkan/loader",
"//hk21/vulkan/sync",
], ],
applicable_licenses = ["//:license"], applicable_licenses = ["//:license"],
) )

View File

@ -14,11 +14,11 @@
#include <SDL3/SDL_vulkan.h> #include <SDL3/SDL_vulkan.h>
#include "hk21/vulkan/loader/loader.hpp" #include "hk21/vulkan/loader/loader.hpp"
#include "hk21/vulkan/sync/sync.hpp"
// @Todo Make fences recyclable // @Todo Make fences recyclable
// @Todo Make command pool recyclable // @Todo Make command pool recyclable
// @Todo Make frame structure recyclable // @Todo Make frame structure recyclable
// @Todo Auto barriers for images
#define VK_ALLOCATOR nullptr #define VK_ALLOCATOR nullptr
@ -288,8 +288,53 @@ static asl::status_or<VkDevice> create_device(VkPhysicalDevice physical_device,
struct FrameResources : asl::intrusive_list_node<FrameResources> struct FrameResources : asl::intrusive_list_node<FrameResources>
{ {
VkFence complete_fence = VK_NULL_HANDLE; VkFence complete_fence = VK_NULL_HANDLE;
VkCommandPool command_pool = VK_NULL_HANDLE; VkCommandPool command_pool = VK_NULL_HANDLE;
};
class DependencyInfoBuilder : public vulkan_sync::DependencyInfoBuilder
{
// @Todo Configure allocator
asl::buffer<VkImageMemoryBarrier2> m_image_barriers;
asl::buffer<VkBufferMemoryBarrier2> 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<uint32_t>(m_buffer_barriers.size()),
.pBufferMemoryBarriers = m_buffer_barriers.data(),
.imageMemoryBarrierCount = static_cast<uint32_t>(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 class GpuImpl : public Gpu
@ -303,8 +348,14 @@ class GpuImpl : public Gpu
VkDevice m_device; VkDevice m_device;
VkQueue m_queue; VkQueue m_queue;
struct Image
{
vulkan_sync::ImageState state;
VkImage image{};
};
asl::option<VkSwapchainKHR> m_swapchain; asl::option<VkSwapchainKHR> m_swapchain;
asl::buffer<VkImage> m_swapchain_images; asl::buffer<Image> m_swapchain_images;
VkSemaphore m_swapchain_image_acquire_semaphore = VK_NULL_HANDLE; VkSemaphore m_swapchain_image_acquire_semaphore = VK_NULL_HANDLE;
VkSemaphore m_queue_complete_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::GlobalHeap m_allocator; // @Todo Make this configurable
asl::IntrusiveList<FrameResources> m_in_flight_frames; asl::IntrusiveList<FrameResources> m_in_flight_frames;
DependencyInfoBuilder m_dependency_builder;
public: public:
GpuImpl( GpuImpl(
VkInstance instance, VkInstance instance,
@ -451,11 +504,24 @@ public:
m_swapchain = swapchain; m_swapchain = swapchain;
vkGetSwapchainImagesKHR(m_device, m_swapchain.value(), &count, nullptr); 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<VkImage> 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)", ASL_LOG_INFO("Vulkan swapchain created ({}x{} with {} images)",
@ -519,6 +585,8 @@ public:
return asl::runtime_error("Couldn't acquire swapchain image: {}", res); return asl::runtime_error("Couldn't acquire swapchain image: {}", res);
} }
auto& swapchain_image = m_swapchain_images[image_index];
VkCommandPoolCreateInfo command_pool_create_info{ VkCommandPoolCreateInfo command_pool_create_info{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.pNext = nullptr, .pNext = nullptr,
@ -561,40 +629,12 @@ public:
return asl::runtime_error("Couldn't begin command buffer: {}", res); return asl::runtime_error("Couldn't begin command buffer: {}", res);
} }
VkImageMemoryBarrier2 barrier1{ vulkan_sync::synchronize_resource(
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, swapchain_image.image,
.pNext = nullptr, VK_IMAGE_ASPECT_COLOR_BIT,
.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, &swapchain_image.state,
.srcAccessMask = VK_ACCESS_2_NONE, vulkan_sync::Usage::kImageClear,
.dstStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT, &m_dependency_builder);
.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);
VkClearColorValue clear_color{ VkClearColorValue clear_color{
.float32 = { 0.0F, 0.137F, 0.4F, 1.0F }, .float32 = { 0.0F, 0.137F, 0.4F, 1.0F },
@ -603,47 +643,23 @@ public:
VkImageSubresourceRange range{ VkImageSubresourceRange range{
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0, .baseMipLevel = 0,
.levelCount = 1, .levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0, .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{ vulkan_sync::synchronize_resource(
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, swapchain_image.image,
.pNext = nullptr, VK_IMAGE_ASPECT_COLOR_BIT,
.srcStageMask = VK_PIPELINE_STAGE_2_CLEAR_BIT, &swapchain_image.state,
.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT, vulkan_sync::Usage::kImagePresent,
.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT, &m_dependency_builder);
.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,
},
};
VkDependencyInfo dependency_info2{ m_dependency_builder.apply(command_buffer);
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.pNext = nullptr,
.dependencyFlags = 0,
.memoryBarrierCount = 0,
.pMemoryBarriers = nullptr,
.bufferMemoryBarrierCount = 0,
.pBufferMemoryBarriers = nullptr,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &barrier2,
};
vkCmdPipelineBarrier2(command_buffer, &dependency_info2);
res = vkEndCommandBuffer(command_buffer); res = vkEndCommandBuffer(command_buffer);
if (res != VK_SUCCESS) if (res != VK_SUCCESS)

View File

@ -1,16 +1,3 @@
# Copyright 2025 Steven Le Rouzic # Copyright 2025 Steven Le Rouzic
# #
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
cc_library(
name = "vulkan",
hdrs = [
"vulkan.hpp",
],
deps = [
"//vendor/vulkan",
"@asl//asl/base",
],
visibility = ["//:__subpackages__"],
applicable_licenses = ["//:license"],
)

View File

@ -12,7 +12,7 @@ cc_library(
"fns.hpp", "fns.hpp",
], ],
deps = [ deps = [
"//hk21/vulkan", "//vendor/vulkan",
"@asl//asl/base", "@asl//asl/base",
"@asl//asl/types:status", "@asl//asl/types:status",
], ],

View File

@ -7,7 +7,8 @@
#include <asl/base/integers.hpp> #include <asl/base/integers.hpp>
#include <asl/types/status.hpp> #include <asl/types/status.hpp>
#include "hk21/vulkan/vulkan.hpp" #include <vulkan.h>
#include "hk21/vulkan/loader/fns.hpp" #include "hk21/vulkan/loader/fns.hpp"
#define FN(NAME) extern PFN_##NAME NAME; #define FN(NAME) extern PFN_##NAME NAME;

View File

@ -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",
],
)

252
hk21/vulkan/sync/sync.cpp Normal file
View File

@ -0,0 +1,252 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "hk21/vulkan/sync/sync.hpp"
#include <asl/types/option.hpp>
// 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<VkImageMemoryBarrier2> 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

64
hk21/vulkan/sync/sync.hpp Normal file
View File

@ -0,0 +1,64 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <asl/base/integers.hpp>
#include <asl/base/utility.hpp>
#include <asl/types/span.hpp>
#include <vulkan.h>
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

View File

@ -0,0 +1,162 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "hk21/vulkan/sync/sync.hpp"
#include <asl/containers/buffer.hpp>
#include <asl/testing/testing.hpp>
class DependencyInfoBuilder : public vulkan_sync::DependencyInfoBuilder
{
asl::buffer<VkImageMemoryBarrier2> m_image_barriers;
asl::buffer<VkBufferMemoryBarrier2> 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<const VkImageMemoryBarrier2> image_barriers() const
{
return m_image_barriers;
}
asl::span<const VkBufferMemoryBarrier2> 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);
}

View File

@ -1,12 +0,0 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <asl/base/integers.hpp>
#define VK_NO_STDDEF_H
#define VK_NO_STDINT_H
#define VK_NO_PROTOTYPES
#include <vulkan.h>

View File

@ -21,6 +21,11 @@ cc_library(
"vulkan.h", "vulkan.h",
"vulkan_core.h", "vulkan_core.h",
], ],
defines = [
"VK_NO_STDDER_H",
"VK_NO_STDINT_H",
"VK_NO_PROTOTYPES",
],
includes = [ includes = [
".", ".",
], ],