From 5d25c1f825840a0ade3b8a41423b6205d2c4f275 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Mon, 19 Oct 2026 03:03:16 +0200 Subject: [PATCH] test(rt): BuildTLAS's read barrier makes the build visible to TLAS reads TLASBuildBarrier drives BuildTlasReadBarrier with no GPU device: the dependency starts at the acceleration-structure build, reaches the ray-tracing and compute shader stages, and carries the acceleration-structure write -> read memory dependency that the old execution-only barrier lacked. Co-Authored-By: Claude Opus 5.5 --- project.cpp | 26 +++++++++++++++++ tests/TLASBuildBarrier/main.cpp | 51 +++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 tests/TLASBuildBarrier/main.cpp diff --git a/project.cpp b/project.cpp index 7d7ae4f..7c1ba12 100644 --- a/project.cpp +++ b/project.cpp @@ -502,6 +502,32 @@ extern "C" Configuration CrafterBuildProject(std::span a tlc.GetInterfacesAndImplementations(ifaces, tlasImpls); cfg.tests.push_back(std::move(tlasTest)); + // The barrier BuildTLAS records after the build carries the + // acceleration-structure memory dependency to the shaders that read + // the TLAS, not just execution order. BuildTlasReadBarrier is pure + // CPU logic, so no GPU device is needed at runtime. + Test tlasBarrierTest; + Configuration& tbc = tlasBarrierTest.config; + tbc.path = cfg.path; + tbc.name = "TLASBuildBarrier"; + tbc.outputName = "TLASBuildBarrier"; + tbc.type = ConfigurationType::Executable; + tbc.target = cfg.target; + tbc.march = cfg.march; + tbc.mtune = cfg.mtune; + tbc.debug = cfg.debug; + tbc.sysroot = cfg.sysroot; + tbc.dependencies = cfg.dependencies; + tbc.externalDependencies = cfg.externalDependencies; + tbc.compileFlags = cfg.compileFlags; + tbc.linkFlags = cfg.linkFlags; + tbc.defines = cfg.defines; + tbc.cFiles = cfg.cFiles; + std::vector tlasBarrierImpls(impls.begin(), impls.end()); + tlasBarrierImpls.emplace_back("tests/TLASBuildBarrier/main"); + tbc.GetInterfacesAndImplementations(ifaces, tlasBarrierImpls); + cfg.tests.push_back(std::move(tlasBarrierTest)); + // Issue #118: the per-frame TLAS instance+metadata host rebuild copies // (and flushes) only the slots whose element's host-authored data // changed, tracked via RenderingElement3D::hostDataVersion against the diff --git a/tests/TLASBuildBarrier/main.cpp b/tests/TLASBuildBarrier/main.cpp new file mode 100644 index 0000000..f57b8e2 --- /dev/null +++ b/tests/TLASBuildBarrier/main.cpp @@ -0,0 +1,51 @@ +//SPDX-License-Identifier: LGPL-3.0-only +//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® + +// The dependency RenderingElement3D::BuildTLAS records after its build, which +// orders the build before the shaders that read the TLAS: traceRayEXT in a +// ray-tracing pass, rayQueryEXT in a compute pass, recorded after it in the +// same command buffer (BuildTLAS in onUpdate, then RTPass, is the documented +// per-frame flow). It used to be an execution-only barrier with no memory +// barrier, which orders the commands but never makes the build's +// acceleration-structure writes available and visible to those reads. The +// dependency is built by BuildTlasReadBarrier, pure CPU logic that this test +// drives directly with no GPU device, like SwapchainBarrierScope. + +#include +#include "vulkan/vulkan.h" + +import Crafter.Graphics; +import std; + +using namespace Crafter; + +namespace { + +int failures = 0; + +void Check(bool ok, std::string_view what) { + std::println("{} {}", ok ? "PASS" : "FAIL", what); + if (!ok) ++failures; +} + +} // namespace + +int main() { + const TlasReadBarrier barrier = BuildTlasReadBarrier(); + + Check(barrier.srcStage == VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + "the dependency starts at the acceleration-structure build"); + Check((barrier.dstStage & VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR) != 0, + "it reaches ray-tracing shaders (traceRayEXT)"); + Check((barrier.dstStage & VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT) != 0, + "it reaches compute shaders (rayQueryEXT)"); + + Check(barrier.memory.sType == VK_STRUCTURE_TYPE_MEMORY_BARRIER, "it carries a memory barrier"); + Check((barrier.memory.srcAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR) != 0, + "the build's acceleration-structure writes are made available"); + Check((barrier.memory.dstAccessMask & VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR) != 0, + "and visible to acceleration-structure reads"); + + std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed"); + return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +}