test(vulkan-rt): headless BLAS build-options + refit test (#36)
Exercises the real hardware AS-build path: builds and refits triangle and procedural BLASes with fast-build/fast-trace + allow-update flags via one-time command buffers, asserting the requested flags land, that an allowUpdate refit keeps the same AS handle/blasAddr (in-place UPDATE), that the no-update / topology-change fallback still produces a valid BLAS, and that the validation layer reports zero errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1a81f115c3
commit
ce0114607a
2 changed files with 239 additions and 0 deletions
209
tests/BLASBuildOptions/main.cpp
Normal file
209
tests/BLASBuildOptions/main.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License version 3.0 as published by the Free Software Foundation;
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// Issue #36: BLAS build options — fast-build / fast-trace preference and
|
||||
// in-place refit (UPDATE-mode rebuild). This exercises the real hardware
|
||||
// path: it spins up a headless Vulkan device (no swapchain / window needed
|
||||
// — a BLAS build only touches the queue + command pool), records BLAS
|
||||
// builds and refits into one-time command buffers, and submits them.
|
||||
//
|
||||
// What is asserted:
|
||||
// - Build() with RTBuildOptions records the requested preference + the
|
||||
// ALLOW_UPDATE bit, and produces a non-zero device address.
|
||||
// - Refit() on an allowUpdate BLAS keeps the SAME acceleration-structure
|
||||
// handle and blasAddr (proof it took the in-place UPDATE path, so TLAS
|
||||
// instances referencing it stay valid).
|
||||
// - Refit() without allowUpdate, or after a topology change, falls back to
|
||||
// a fresh build (new handle / address are fine; it must still succeed).
|
||||
// - The procedural (AABB) path supports the same options + refit.
|
||||
// - The Vulkan validation layer reports ZERO errors across all of the
|
||||
// above (Device::validationErrorCount) — the strongest check that the
|
||||
// UPDATE builds are spec-correct (scratch sizing, ALLOW_UPDATE present,
|
||||
// src==dst, matching topology, …).
|
||||
//
|
||||
// Validation layers are required for the last check to be meaningful; the
|
||||
// build marks this test as needing the SDK layers.
|
||||
|
||||
#include "vulkan/vulkan.h"
|
||||
#include <cstdlib>
|
||||
|
||||
import Crafter.Graphics;
|
||||
import Crafter.Math;
|
||||
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;
|
||||
}
|
||||
|
||||
// One-time command buffer helpers — record a BLAS build, submit, block.
|
||||
VkCommandBuffer BeginCmd() {
|
||||
VkCommandBufferAllocateInfo allocInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
||||
.commandPool = Device::commandPool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
||||
.commandBufferCount = 1,
|
||||
};
|
||||
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
||||
Device::CheckVkResult(vkAllocateCommandBuffers(Device::device, &allocInfo, &cmd));
|
||||
VkCommandBufferBeginInfo beginInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
||||
};
|
||||
Device::CheckVkResult(vkBeginCommandBuffer(cmd, &beginInfo));
|
||||
return cmd;
|
||||
}
|
||||
|
||||
void SubmitWait(VkCommandBuffer cmd) {
|
||||
Device::CheckVkResult(vkEndCommandBuffer(cmd));
|
||||
VkSubmitInfo submitInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||
.commandBufferCount = 1,
|
||||
.pCommandBuffers = &cmd,
|
||||
};
|
||||
Device::CheckVkResult(vkQueueSubmit(Device::queue, 1, &submitInfo, VK_NULL_HANDLE));
|
||||
Device::CheckVkResult(vkQueueWaitIdle(Device::queue));
|
||||
vkFreeCommandBuffers(Device::device, Device::commandPool, 1, &cmd);
|
||||
}
|
||||
|
||||
// A unit cube (8 verts, 12 triangles) — enough topology for a real BLAS.
|
||||
std::vector<Vector<float, 3, 3>> CubeVerts(float s) {
|
||||
return {
|
||||
{-s,-s,-s}, { s,-s,-s}, { s, s,-s}, {-s, s,-s},
|
||||
{-s,-s, s}, { s,-s, s}, { s, s, s}, {-s, s, s},
|
||||
};
|
||||
}
|
||||
std::vector<std::uint32_t> CubeIndices() {
|
||||
return {
|
||||
0,1,2, 0,2,3, 4,6,5, 4,7,6,
|
||||
0,4,5, 0,5,1, 3,2,6, 3,6,7,
|
||||
1,5,6, 1,6,2, 0,3,7, 0,7,4,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
Device::Initialize();
|
||||
Device::validationErrorCount = 0;
|
||||
|
||||
// ── Triangle BLAS: fast-trace + allow-update, then in-place refit. ──
|
||||
Mesh tri;
|
||||
{
|
||||
auto verts = CubeVerts(1.0f);
|
||||
auto idx = CubeIndices();
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
tri.Build(verts, idx, cmd, RTBuildOptions{
|
||||
.preference = RTBuildPreference::FastTrace,
|
||||
.allowUpdate = true,
|
||||
});
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(tri.blasAddr != 0, "triangle Build produced a non-zero blasAddr");
|
||||
Check(tri.accelerationStructure != VK_NULL_HANDLE, "triangle Build created an AS handle");
|
||||
Check((tri.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR) != 0,
|
||||
"FastTrace preference → PREFER_FAST_TRACE bit set");
|
||||
Check((tri.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR) != 0,
|
||||
"allowUpdate=true → ALLOW_UPDATE bit set");
|
||||
Check(tri.builtPrimitiveCount == 12, "triangle BLAS reports 12 primitives");
|
||||
|
||||
const VkDeviceAddress triAddrBefore = tri.blasAddr;
|
||||
const VkAccelerationStructureKHR triHandleBefore = tri.accelerationStructure;
|
||||
{
|
||||
// Same topology, deformed positions → must take the UPDATE path.
|
||||
auto verts = CubeVerts(1.5f);
|
||||
auto idx = CubeIndices();
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
tri.Refit(verts, idx, cmd);
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(tri.accelerationStructure == triHandleBefore,
|
||||
"Refit kept the same AS handle (in-place UPDATE)");
|
||||
Check(tri.blasAddr == triAddrBefore,
|
||||
"Refit kept the same blasAddr (instances stay valid)");
|
||||
|
||||
// ── Triangle BLAS: fast-build, no update → flags reflect the choice. ─
|
||||
Mesh triFast;
|
||||
{
|
||||
auto verts = CubeVerts(1.0f);
|
||||
auto idx = CubeIndices();
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
triFast.Build(verts, idx, cmd, RTBuildOptions{ .preference = RTBuildPreference::FastBuild });
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check((triFast.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR) != 0,
|
||||
"FastBuild preference → PREFER_FAST_BUILD bit set");
|
||||
Check((triFast.buildFlags & VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR) == 0,
|
||||
"allowUpdate=false → ALLOW_UPDATE bit clear");
|
||||
|
||||
// Refit without allowUpdate must still succeed via the rebuild fallback.
|
||||
{
|
||||
auto verts = CubeVerts(0.5f);
|
||||
auto idx = CubeIndices();
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
triFast.Refit(verts, idx, cmd);
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(triFast.blasAddr != 0, "Refit fallback rebuild still produced a valid BLAS");
|
||||
|
||||
// ── Procedural (AABB) BLAS: build with options, then refit. ─────────
|
||||
Mesh proc;
|
||||
{
|
||||
std::array<RTAabb, 2> boxes {{
|
||||
{ .min = {-1,-1,-1}, .max = {1,1,1} },
|
||||
{ .min = { 2, 2, 2}, .max = {3,3,3} },
|
||||
}};
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
proc.BuildProcedural(boxes, /*opaque*/ false, cmd, RTBuildOptions{
|
||||
.preference = RTBuildPreference::FastTrace, .allowUpdate = true });
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(proc.blasAddr != 0, "procedural Build produced a non-zero blasAddr");
|
||||
Check(proc.builtPrimitiveCount == 2, "procedural BLAS reports 2 primitives");
|
||||
|
||||
const VkDeviceAddress procAddrBefore = proc.blasAddr;
|
||||
const VkAccelerationStructureKHR procHandleBefore = proc.accelerationStructure;
|
||||
{
|
||||
std::array<RTAabb, 2> boxes {{
|
||||
{ .min = {-2,-2,-2}, .max = {2,2,2} },
|
||||
{ .min = { 4, 4, 4}, .max = {5,5,5} },
|
||||
}};
|
||||
VkCommandBuffer cmd = BeginCmd();
|
||||
proc.RefitProcedural(boxes, cmd);
|
||||
SubmitWait(cmd);
|
||||
}
|
||||
Check(proc.accelerationStructure == procHandleBefore && proc.blasAddr == procAddrBefore,
|
||||
"RefitProcedural kept the same AS handle + blasAddr (in-place UPDATE)");
|
||||
|
||||
Check(Device::validationErrorCount == 0,
|
||||
std::format("no Vulkan validation errors ({} seen)", Device::validationErrorCount));
|
||||
|
||||
if (failures != 0) {
|
||||
std::println("{} check(s) failed", failures);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::println("all checks passed");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue