gpuav: Fix RT validation
Having a map with AS key as addresses can lead to scenarios where
if 2 BLAS happen to have the same address, destroying one will cause
its map entry to be destroyed, even though the other BLAS is still
alive, leading to VVL believing further reference to this common
address are invalid.
diff --git a/layers/gpuav/validation_cmd/gpuav_ray_tracing.cpp b/layers/gpuav/validation_cmd/gpuav_ray_tracing.cpp
index 6ee3166..56c49f2 100644
--- a/layers/gpuav/validation_cmd/gpuav_ray_tracing.cpp
+++ b/layers/gpuav/validation_cmd/gpuav_ray_tracing.cpp
@@ -1,6 +1,6 @@
-/* Copyright (c) 2018-2025 The Khronos Group Inc.
- * Copyright (c) 2018-2025 Valve Corporation
- * Copyright (c) 2018-2025 LunarG, Inc.
+/* Copyright (c) 2018-2026 The Khronos Group Inc.
+ * Copyright (c) 2018-2026 Valve Corporation
+ * Copyright (c) 2018-2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -183,8 +183,9 @@
std::vector<VkWriteDescriptorSet> GetDescriptorWrites() const { return {}; }
};
-struct AccelerationStructuresAddrToStateObjectMap {
- vvl::concurrent_unordered_map<VkDeviceAddress, std::shared_ptr<vvl::AccelerationStructureKHR>> map;
+struct AccelerationStructuresWithAddressesArray {
+ std::shared_mutex map_mutex;
+ std::vector<vvl::AccelerationStructureKHR*> array;
};
void RecordGetAccelerationStructureDeviceAddress(Validator& gpuav, VkAccelerationStructureKHR as, VkDeviceAddress as_addr) {
@@ -195,11 +196,15 @@
if (as_addr == 0) {
return;
}
-
if (auto as_state = gpuav.Get<vvl::AccelerationStructureKHR>(as)) {
as_state->acceleration_structure_address = as_addr;
- auto& as_addr_to_as_buffer = gpuav.shared_resources_cache.GetOrCreate<AccelerationStructuresAddrToStateObjectMap>();
- as_addr_to_as_buffer.map.insert(as_addr, as_state);
+ AccelerationStructuresWithAddressesArray& as_with_addresses =
+ gpuav.shared_resources_cache.GetOrCreate<AccelerationStructuresWithAddressesArray>();
+ WriteLockGuard lock(as_with_addresses.map_mutex);
+ if (as_with_addresses.array.capacity() <= (as_with_addresses.array.size() + 1)) {
+ as_with_addresses.array.reserve(as_with_addresses.array.capacity() * 2);
+ }
+ as_with_addresses.array.emplace_back(as_state.get());
}
}
@@ -207,14 +212,20 @@
if (!gpuav.gpuav_settings.validate_acceleration_structures_builds) {
return;
}
-
if (auto as_state = gpuav.Get<vvl::AccelerationStructureKHR>(as)) {
if (as_state->acceleration_structure_address != 0) {
- auto* as_addr_to_as_buffer = gpuav.shared_resources_cache.TryGet<AccelerationStructuresAddrToStateObjectMap>();
- if (as_addr_to_as_buffer) {
- as_addr_to_as_buffer->map.erase(as_state->acceleration_structure_address);
- as_state->acceleration_structure_address = 0;
+ auto* as_with_addresses = gpuav.shared_resources_cache.TryGet<AccelerationStructuresWithAddressesArray>();
+ if (as_with_addresses) {
+ WriteLockGuard lock(as_with_addresses->map_mutex);
+ const auto as_found_it =
+ std::find(as_with_addresses->array.begin(), as_with_addresses->array.end(), as_state.get());
+ if (as_found_it != as_with_addresses->array.end()) {
+ const size_t i = std::distance(as_with_addresses->array.begin(), as_found_it);
+ std::swap(as_with_addresses->array[i], as_with_addresses->array[as_with_addresses->array.size() - 1]);
+ as_with_addresses->array.resize(as_with_addresses->array.size() - 1);
+ }
}
+ as_state->acceleration_structure_address = 0;
}
}
}
@@ -465,35 +476,36 @@
cb_state.on_pre_cb_submission_functions.emplace_back([as_arrays_ptr_buffer](Validator& gpuav, CommandBufferSubState& cb,
VkCommandBuffer per_submission_cb) {
VVL_ZoneScopedN("validate_as_builds_pre_submit");
- std::vector<std::pair<const VkDeviceAddress, std::shared_ptr<vvl::AccelerationStructureKHR>>> as_addr_to_as_buffer_snapshot;
- auto* as_addr_to_as_buffer = gpuav.shared_resources_cache.TryGet<AccelerationStructuresAddrToStateObjectMap>();
- if (as_addr_to_as_buffer) {
- // #ARNO_TODO Definitely can see this become a big perf bottleneck
- as_addr_to_as_buffer_snapshot = as_addr_to_as_buffer->map.snapshot();
+
+ auto* as_with_addresses = gpuav.shared_resources_cache.TryGet<AccelerationStructuresWithAddressesArray>();
+ if (!as_with_addresses) {
+ return;
}
+ ReadLockGuard lock(as_with_addresses->map_mutex);
+
// valid AS addresses buffer
vko::BufferRange as_addresses_buffer = cb.gpu_resources_manager.GetHostCoherentBufferRange(
- 2 * sizeof(uint32_t) + as_addr_to_as_buffer_snapshot.size() * sizeof(uint64_t));
+ 2 * sizeof(uint32_t) + as_with_addresses->array.size() * sizeof(uint64_t));
auto accel_struct_addresses_buffer_u32_ptr = (uint32_t*)as_addresses_buffer.offset_mapped_ptr;
- *accel_struct_addresses_buffer_u32_ptr = (uint32_t)as_addr_to_as_buffer_snapshot.size();
+ *accel_struct_addresses_buffer_u32_ptr = (uint32_t)as_with_addresses->array.size();
auto as_addresses_ptr = (uint64_t*)(accel_struct_addresses_buffer_u32_ptr + 2);
// valid AS metadata buffer
vko::BufferRange as_metadatas_buffer =
- cb.gpu_resources_manager.GetHostCachedBufferRange(as_addr_to_as_buffer_snapshot.size() * sizeof(uint32_t));
+ cb.gpu_resources_manager.GetHostCachedBufferRange(as_with_addresses->array.size() * sizeof(uint32_t));
auto as_metadatas_ptr = (uint32_t*)(as_metadatas_buffer.offset_mapped_ptr);
// valid AS buffer address ranges buffer
vko::BufferRange as_buffer_addr_ranges_buffer =
- cb.gpu_resources_manager.GetHostCoherentBufferRange(as_addr_to_as_buffer_snapshot.size() * (2 * sizeof(uint64_t)));
+ cb.gpu_resources_manager.GetHostCoherentBufferRange(as_with_addresses->array.size() * (2 * sizeof(uint64_t)));
auto as_buffer_addr_ranges_ptr = (uint64_t*)(as_buffer_addr_ranges_buffer.offset_mapped_ptr);
uint32_t written_count = 0;
- for (const auto& [device_addr, as] : as_addr_to_as_buffer_snapshot) {
- as_addresses_ptr[written_count] = device_addr;
+ for (const vvl::AccelerationStructureKHR* as : as_with_addresses->array) {
+ as_addresses_ptr[written_count] = as->acceleration_structure_address;
const uint32_t metadata = uint32_t(as->buffer_state && !as->buffer_state->Destroyed());
as_metadatas_ptr[written_count] = metadata;
const vvl::range<VkDeviceAddress> as_buffer_addr_range = as->GetDeviceAddressRange();
@@ -645,15 +657,18 @@
break;
}
case kErrorSubCode_PreBuildAccelerationStructures_DestroyedASBuffer: {
- auto& as_addr_to_as_buffer = gpuav.shared_resources_cache.Get<AccelerationStructuresAddrToStateObjectMap>();
- auto found_as = as_addr_to_as_buffer.map.find(blas_in_tlas_addr);
- std::ostringstream ss_as;
- std::ostringstream ss_buffer;
- if (found_as != as_addr_to_as_buffer.map.end()) {
+ auto& as_with_addresses = gpuav.shared_resources_cache.Get<AccelerationStructuresWithAddressesArray>();
+ const auto as_found_it = std::find_if(as_with_addresses.array.begin(), as_with_addresses.array.end(),
+ [blas_in_tlas_addr](vvl::AccelerationStructureKHR* as) {
+ return as->acceleration_structure_address == blas_in_tlas_addr;
+ });
+ std::stringstream ss_as;
+ std::stringstream ss_buffer;
+ if (as_found_it != as_with_addresses.array.end()) {
ss_as << "Acceleration structure corresponding to reference: "
- << gpuav.FormatHandle(found_as->second->VkHandle());
- if (found_as->second->buffer_state) {
- ss_buffer << "(" << gpuav.FormatHandle(found_as->second->buffer_state->VkHandle()) << ") ";
+ << gpuav.FormatHandle((*as_found_it)->VkHandle());
+ if ((*as_found_it)->buffer_state) {
+ ss_buffer << "(" << gpuav.FormatHandle((*as_found_it)->buffer_state->VkHandle()) << ") ";
}
} else {
ss_as << "Could not map acceleration structure reference to its corresponding handle, this is most likely a "
@@ -671,25 +686,28 @@
case kErrorSubCode_PreBuildAccelerationStructures_BlasMemoryOverlap: {
const uint32_t blas_built_in_cmd_i = error_record[kValCmdErrorPayloadDword_4];
const BlasBuiltInCmd& blas_built_in_cmd = blas_built_in_cmd_array[blas_built_in_cmd_i];
- auto& as_addr_to_as_buffer = gpuav.shared_resources_cache.Get<AccelerationStructuresAddrToStateObjectMap>();
- auto found_as = as_addr_to_as_buffer.map.find(blas_in_tlas_addr);
- std::ostringstream error_ss;
- if (found_as != as_addr_to_as_buffer.map.end()) {
- const vvl::range<VkDeviceAddress> blas_in_tlas_buffer_addr_range = found_as->second->GetDeviceAddressRange();
+ auto& as_with_addresses = gpuav.shared_resources_cache.Get<AccelerationStructuresWithAddressesArray>();
+ const auto as_found_it = std::find_if(as_with_addresses.array.begin(), as_with_addresses.array.end(),
+ [blas_in_tlas_addr](vvl::AccelerationStructureKHR* as) {
+ return as->acceleration_structure_address == blas_in_tlas_addr;
+ });
+ std::stringstream error_ss;
+ if (as_found_it != as_with_addresses.array.end()) {
+ const vvl::range<VkDeviceAddress> blas_in_tlas_buffer_addr_range = (*as_found_it)->GetDeviceAddressRange();
const vvl::range<VkDeviceAddress> blas_built_in_cmd_buffer_addr_range =
blas_built_in_cmd.blas->GetDeviceAddressRange();
const vvl::range<VkDeviceAddress> overlap =
blas_in_tlas_buffer_addr_range & blas_built_in_cmd_buffer_addr_range;
assert(overlap.non_empty());
const VkAccelerationStructureKHR blas_built_in_cmd_handle = blas_built_in_cmd.blas->VkHandle();
- const VkAccelerationStructureKHR blas_in_tlas_handle = found_as->second->VkHandle();
+ const VkAccelerationStructureKHR blas_in_tlas_handle = (*as_found_it)->VkHandle();
if (blas_built_in_cmd_handle != blas_in_tlas_handle) {
error_ss << "pInfos[" << blas_built_in_cmd.p_info_i << "].dstAccelerationStructure ("
<< gpuav.FormatHandle(blas_built_in_cmd.blas->VkHandle()) << "), backed by buffer ("
<< gpuav.FormatHandle(blas_built_in_cmd.blas->buffer_state->VkHandle())
<< "), overlaps on buffer address range " << vvl::string_range_hex(overlap) << " with buffer ("
- << gpuav.FormatHandle(found_as->second->buffer_state->VkHandle()) << ") of BLAS ("
- << gpuav.FormatHandle(found_as->second->VkHandle()) << "), referenced in " << invalid_blas_loc_str;
+ << gpuav.FormatHandle((*as_found_it)->buffer_state->VkHandle()) << ") of BLAS ("
+ << gpuav.FormatHandle((*as_found_it)->VkHandle()) << "), referenced in " << invalid_blas_loc_str;
} else {
error_ss << "pInfos[" << blas_built_in_cmd.p_info_i << "].dstAccelerationStructure ("
<< gpuav.FormatHandle(blas_built_in_cmd.blas->VkHandle())
diff --git a/tests/unit/gpu_av_ray_tracing_positive.cpp b/tests/unit/gpu_av_ray_tracing_positive.cpp
index 27d6cb7..e00c462 100644
--- a/tests/unit/gpu_av_ray_tracing_positive.cpp
+++ b/tests/unit/gpu_av_ray_tracing_positive.cpp
@@ -1,8 +1,8 @@
/*
- * Copyright (c) 2015-2025 The Khronos Group Inc.
- * Copyright (c) 2015-2025 Valve Corporation
- * Copyright (c) 2015-2025 LunarG, Inc.
- * Copyright (c) 2015-2025 Google, Inc.
+ * Copyright (c) 2015-2026 The Khronos Group Inc.
+ * Copyright (c) 2015-2026 Valve Corporation
+ * Copyright (c) 2015-2026 LunarG, Inc.
+ * Copyright (c) 2015-2026 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1085,3 +1085,227 @@
ASSERT_EQ(debug_buffer_ptr[1], 0);
ASSERT_EQ(debug_buffer_ptr[2], 1);
}
+
+TEST_F(PositiveGpuAVRayTracing, TraceRaysInCubes2) {
+ TEST_DESCRIPTION(
+ "Setup a RT pipeline, a TLAS pointing to 2 cubes, and traces rays into it. Have 2 identical BLAS backed by the same "
+ "memory, so that they have the same address, and make sure deleting one when only the other one is used does not lead to "
+ "false positives.");
+
+ RETURN_IF_SKIP(CheckSlangSupport());
+
+ SetTargetApiVersion(VK_API_VERSION_1_2);
+
+ AddRequiredExtensions(VK_KHR_MAINTENANCE_4_EXTENSION_NAME);
+ AddRequiredFeature(vkt::Feature::rayTracingPipeline);
+ AddRequiredFeature(vkt::Feature::accelerationStructure);
+ AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
+ AddRequiredFeature(vkt::Feature::maintenance4);
+ AddRequiredFeature(vkt::Feature::shaderInt64);
+
+ VkValidationFeaturesEXT validation_features = GetGpuAvValidationFeatures();
+ RETURN_IF_SKIP(InitFrameworkForRayTracingTest(&validation_features));
+ if (!CanEnableGpuAV(*this)) {
+ GTEST_SKIP() << "Requirements for GPU-AV are not met";
+ }
+ RETURN_IF_SKIP(InitState());
+ InitRenderTarget();
+
+ vkt::as::GeometryKHR cube(vkt::as::blueprint::GeometryCubeOnDeviceInfo(*m_device));
+ vkt::as::BuildGeometryInfoKHR cube_blas = vkt::as::blueprint::BuildGeometryInfoOnDeviceBottomLevel(*m_device, std::move(cube));
+
+ const VkAccelerationStructureBuildSizesInfoKHR size_info = cube_blas.GetSizeInfo();
+ VkBufferCreateInfo blas_buffer_ci = vku::InitStructHelper();
+ blas_buffer_ci.size = size_info.accelerationStructureSize;
+ blas_buffer_ci.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR |
+ VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
+ vkt::Buffer blas_buffer(*m_device, blas_buffer_ci, vkt::no_mem);
+ VkMemoryRequirements mem_reqs{};
+ vk::GetBufferMemoryRequirements(device(), blas_buffer, &mem_reqs);
+ VkMemoryAllocateFlagsInfo alloc_flags = vku::InitStructHelper();
+ alloc_flags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT;
+ VkMemoryAllocateInfo alloc_info = vku::InitStructHelper(&alloc_flags);
+ alloc_info.allocationSize = std::max(size_info.accelerationStructureSize, mem_reqs.size);
+ vkt::DeviceMemory blas_buffer_memory(*m_device, alloc_info);
+ blas_buffer.BindMemory(blas_buffer_memory, 0);
+
+ cube_blas.GetDstAS()->SetDeviceBuffer(std::move(blas_buffer));
+
+ m_command_buffer.Begin();
+ cube_blas.BuildCmdBuffer(m_command_buffer);
+ m_command_buffer.End();
+
+ m_default_queue->Submit(m_command_buffer);
+ m_device->Wait();
+
+ // Create another blas, with a buffer backed by the same memory
+ vkt::as::GeometryKHR cube_2(vkt::as::blueprint::GeometryCubeOnDeviceInfo(*m_device));
+ vkt::as::BuildGeometryInfoKHR cube_2_blas =
+ vkt::as::blueprint::BuildGeometryInfoOnDeviceBottomLevel(*m_device, std::move(cube_2));
+ vkt::Buffer blas_buffer_2(*m_device, blas_buffer_ci, vkt::no_mem);
+ blas_buffer_2.BindMemory(blas_buffer_memory, 0);
+ cube_2_blas.GetDstAS()->SetDeviceBuffer(std::move(blas_buffer_2));
+
+ m_command_buffer.Begin();
+ cube_2_blas.BuildCmdBuffer(m_command_buffer);
+ m_command_buffer.End();
+
+ m_default_queue->Submit(m_command_buffer);
+ m_device->Wait();
+
+ std::vector<vkt::as::GeometryKHR> cube_instances(1);
+ cube_instances[0].SetType(vkt::as::GeometryKHR::Type::Instance);
+
+ VkAccelerationStructureInstanceKHR cube_instance_1{};
+ cube_instance_1.transform.matrix[0][0] = 1.0f;
+ cube_instance_1.transform.matrix[1][1] = 1.0f;
+ cube_instance_1.transform.matrix[2][2] = 1.0f;
+ cube_instance_1.transform.matrix[0][3] = 50.0f;
+ cube_instance_1.transform.matrix[1][3] = 0.0f;
+ cube_instance_1.transform.matrix[2][3] = 0.0f;
+ cube_instance_1.mask = 0xff;
+ cube_instance_1.instanceCustomIndex = 0;
+ // Cube instance 1 will be associated to closest hit shader 1
+ cube_instance_1.instanceShaderBindingTableRecordOffset = 0;
+ cube_instances[0].AddInstanceDeviceAccelStructRef(*m_device, cube_2_blas.GetDstAS()->handle(), cube_instance_1);
+
+ VkAccelerationStructureInstanceKHR cube_instance_2{};
+ cube_instance_2.transform.matrix[0][0] = 1.0f;
+ cube_instance_2.transform.matrix[1][1] = 1.0f;
+ cube_instance_2.transform.matrix[2][2] = 1.0f;
+ cube_instance_2.transform.matrix[0][3] = 0.0f;
+ cube_instance_2.transform.matrix[1][3] = 0.0f;
+ cube_instance_2.transform.matrix[2][3] = 50.0f;
+ cube_instance_2.mask = 0xff;
+ cube_instance_2.instanceCustomIndex = 0;
+ // Cube instance 2 will be associated to closest hit shader 1
+ cube_instance_2.instanceShaderBindingTableRecordOffset = 0;
+
+ cube_instances[0].AddInstanceDeviceAccelStructRef(*m_device, cube_2_blas.GetDstAS()->handle(), cube_instance_2);
+
+ {
+ std::vector<vkt::as::GeometryKHR> cube_instances_foo(1);
+ cube_instances_foo[0].SetType(vkt::as::GeometryKHR::Type::Instance);
+
+ VkAccelerationStructureInstanceKHR cube_instance_1_foo{};
+ cube_instance_1_foo.transform.matrix[0][0] = 1.0f;
+ cube_instance_1_foo.transform.matrix[1][1] = 1.0f;
+ cube_instance_1_foo.transform.matrix[2][2] = 1.0f;
+ cube_instance_1_foo.transform.matrix[0][3] = 50.0f;
+ cube_instance_1_foo.transform.matrix[1][3] = 0.0f;
+ cube_instance_1_foo.transform.matrix[2][3] = 0.0f;
+ cube_instance_1_foo.mask = 0xff;
+ cube_instance_1_foo.instanceCustomIndex = 0;
+ // Cube instance 1 will be associated to closest hit shader 1
+ cube_instance_1_foo.instanceShaderBindingTableRecordOffset = 0;
+ cube_instances_foo[0].AddInstanceDeviceAccelStructRef(*m_device, cube_blas.GetDstAS()->handle(), cube_instance_1_foo);
+
+ VkAccelerationStructureInstanceKHR cube_instance_2_foo{};
+ cube_instance_2_foo.transform.matrix[0][0] = 1.0f;
+ cube_instance_2_foo.transform.matrix[1][1] = 1.0f;
+ cube_instance_2_foo.transform.matrix[2][2] = 1.0f;
+ cube_instance_2_foo.transform.matrix[0][3] = 0.0f;
+ cube_instance_2_foo.transform.matrix[1][3] = 0.0f;
+ cube_instance_2_foo.transform.matrix[2][3] = 50.0f;
+ cube_instance_2_foo.mask = 0xff;
+ cube_instance_2_foo.instanceCustomIndex = 0;
+ // Cube instance 2 will be associated to closest hit shader 1
+ cube_instance_2_foo.instanceShaderBindingTableRecordOffset = 0;
+
+ cube_instances_foo[0].AddInstanceDeviceAccelStructRef(*m_device, cube_blas.GetDstAS()->handle(), cube_instance_2_foo);
+ }
+
+ cube_blas.GetDstAS()->Destroy();
+
+ std::vector<vkt::as::BuildGeometryInfoKHR> tlas_build_info;
+ {
+ vkt::as::BuildGeometryInfoKHR tlas = vkt::as::blueprint::CreateTLAS(*m_device, std::move(cube_instances));
+ tlas_build_info.emplace_back(std::move(tlas));
+ m_command_buffer.Begin();
+ vkt::as::BuildAccelerationStructuresKHR(m_command_buffer, tlas_build_info);
+ m_command_buffer.End();
+
+ m_default_queue->Submit(m_command_buffer);
+ m_device->Wait();
+ }
+ // Buffer used to count invocations for the 3 shaders
+ vkt::Buffer debug_buffer(*m_device, 3 * sizeof(uint32_t), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ kHostVisibleMemProps);
+ auto debug_buffer_ptr = static_cast<uint32_t*>(debug_buffer.Memory().Map());
+ std::memset(debug_buffer_ptr, 0, (size_t)debug_buffer.CreateInfo().size);
+
+ const char* slang_shader = R"slang(
+ [[vk::binding(0, 0)]] uniform RaytracingAccelerationStructure tlas;
+ [[vk::binding(1, 0)]] RWStructuredBuffer<uint32_t> debug_buffer;
+
+ struct RayPayload {
+ uint4 payload;
+ float3 hit;
+ };
+
+ [shader("raygeneration")]
+ void rayGenShader()
+ {
+ InterlockedAdd(debug_buffer[0], 1);
+ RayPayload ray_payload = {};
+ RayDesc ray;
+ ray.TMin = 0.01;
+ ray.TMax = 1000.0;
+
+ // Will hit cube 1
+ ray.Origin = float3(0,0,0);
+ ray.Direction = float3(1,0,0);
+ TraceRay(tlas, RAY_FLAG_NONE, 0xff, 0, 0, 0, ray, ray_payload);
+ }
+
+ [shader("miss")]
+ void missShader(inout RayPayload payload)
+ {
+ InterlockedAdd(debug_buffer[1], 1);
+ payload.hit = float3(0.1, 0.2, 0.3);
+ }
+
+ [shader("closesthit")]
+ void closestHitShader(inout RayPayload payload, in BuiltInTriangleIntersectionAttributes attr)
+ {
+ InterlockedAdd(debug_buffer[2], 1);
+ const float3 barycentric_coords = float3(1.0f - attr.barycentrics.x - attr.barycentrics.y, attr.barycentrics.x,
+ attr.barycentrics.y);
+ payload.hit = barycentric_coords;
+ }
+ )slang";
+
+ vkt::rt::Pipeline pipeline(*this, m_device);
+ pipeline.AddSlangRayGenShader(slang_shader, "rayGenShader");
+ pipeline.AddSlangMissShader(slang_shader, "missShader");
+ pipeline.AddSlangClosestHitShader(slang_shader, "closestHitShader");
+
+ pipeline.AddBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 0);
+ pipeline.AddBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1);
+ pipeline.CreateDescriptorSet();
+
+ pipeline.Build();
+
+ pipeline.GetDescriptorSet().WriteDescriptorAccelStruct(0, 1, &tlas_build_info[0].GetDstAS()->handle());
+ pipeline.GetDescriptorSet().WriteDescriptorBufferInfo(1, debug_buffer, 0, VK_WHOLE_SIZE, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
+ pipeline.GetDescriptorSet().UpdateDescriptorSets();
+
+ m_command_buffer.Begin();
+
+ vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline.GetPipelineLayout(), 0, 1,
+ &pipeline.GetDescriptorSet().set_, 0, nullptr);
+ vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
+
+ vkt::rt::TraceRaysSbt sbt_ray_gen_1 = pipeline.GetTraceRaysSbt(0);
+ vk::CmdTraceRaysKHR(m_command_buffer, &sbt_ray_gen_1.ray_gen_sbt, &sbt_ray_gen_1.miss_sbt, &sbt_ray_gen_1.hit_sbt,
+ &sbt_ray_gen_1.callable_sbt, 1, 1, 1);
+
+ m_command_buffer.End();
+
+ m_default_queue->SubmitAndWait(m_command_buffer);
+
+ // Make sure expected ray tracing setup worked, indicating the TLAS was correctly built
+ ASSERT_EQ(debug_buffer_ptr[0], 1);
+ ASSERT_EQ(debug_buffer_ptr[1], 0);
+ ASSERT_EQ(debug_buffer_ptr[2], 1);
+}