test(ui): the descriptor-set fallback draws the same frame as the heap
UIFallbackParity renders one scene through both binding models and compares the frames. It starts itself twice, once as is and once with CRAFTER_GRAPHICS_NO_DESCRIPTOR_HEAP, so the fallback cannot drift from the heap path on a machine that never takes it. The scene reaches what HelloUI does not: the four standalone kernels next to the fused one, a sampled image with two samplers indexed per item, an image slot and a sampler slot that are freed and registered again, a resize, an image created with ImageVulkan::Create's default consumer stage, and a generic Dispatch whose push payload is not a whole number of words. On a driver without the heap both runs take the fallback, and the test then only checks that the scene renders. Validation errors raised while rendering fail a run, which only bites when CRAFTER_GRAPHICS_VALIDATION is set. They are counted before the frame capture because Window::SaveFrame raises one itself (#157). UIFusedShader now also compiles and validates the descriptor-set build of the fused kernel and pins its push-constant layout: both builds are fed the same UIFusedHeader bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
48aa9be5ce
commit
2b6f90b12c
3 changed files with 513 additions and 43 deletions
|
|
@ -18,7 +18,11 @@
|
|||
// UIFusedHeader (four uvec4 headers at 0/16/32/48 then four vec4 clip
|
||||
// rects at 64/80/96/112, 128 bytes total). Any add/remove/reorder of a
|
||||
// member shifts these offsets and fails here, catching a GLSL/C++ drift
|
||||
// that the C++-side static_assert(sizeof==128) alone cannot.
|
||||
// that the C++-side static_assert(sizeof==128) alone cannot;
|
||||
// 5. repeats steps 1-4 for the descriptor-set build of the same source
|
||||
// (-DCRAFTER_GRAPHICS_DESCRIPTOR_SETS, shipped as ui-fused.comp.sets.spv
|
||||
// for drivers without VK_EXT_descriptor_heap). Both builds are fed the
|
||||
// same UIFusedHeader bytes, so both must keep the same block layout.
|
||||
//
|
||||
// glslang and spirv-val are invoked at runtime, so the test declares them as
|
||||
// required tools (see project.cpp). It needs no GPU device.
|
||||
|
|
@ -136,52 +140,61 @@ int main() {
|
|||
const fs::path dir = fs::temp_directory_path() / "crafter-uifused-test";
|
||||
std::error_code ec;
|
||||
fs::create_directories(dir, ec);
|
||||
const fs::path spv = dir / "ui-fused.comp.spv";
|
||||
|
||||
// 1. Compile the real uber-kernel. GL_GOOGLE_include resolves
|
||||
// ui-shared.glsl relative to the source directory.
|
||||
std::string compile = "glslang --target-env vulkan1.4 -V -S comp \""
|
||||
+ src.string() + "\" -o \"" + spv.string() + "\" > /dev/null";
|
||||
if (RunCommand(compile) != 0) {
|
||||
std::println(std::cerr, "glslang failed to compile ui-fused.comp.glsl");
|
||||
return 1;
|
||||
// The heap build first, then the descriptor-set build of the same source.
|
||||
struct Variant { std::string_view label; std::string_view define; std::string_view file; };
|
||||
constexpr Variant variants[] = {
|
||||
{ "descriptor-heap", "", "ui-fused.comp.spv" },
|
||||
{ "descriptor-set", "-DCRAFTER_GRAPHICS_DESCRIPTOR_SETS ", "ui-fused.comp.sets.spv" },
|
||||
};
|
||||
for (const Variant& variant : variants) {
|
||||
const fs::path spv = dir / variant.file;
|
||||
|
||||
// 1. Compile the real uber-kernel. GL_GOOGLE_include resolves
|
||||
// ui-shared.glsl relative to the source directory.
|
||||
std::string compile = "glslang --target-env vulkan1.4 " + std::string(variant.define) + "-V -S comp \""
|
||||
+ src.string() + "\" -o \"" + spv.string() + "\" > /dev/null";
|
||||
if (RunCommand(compile) != 0) {
|
||||
std::println(std::cerr, "glslang failed to compile ui-fused.comp.glsl ({} build)", variant.label);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> words = ReadSpirv(spv);
|
||||
if (words.size() < 5) {
|
||||
std::println(std::cerr, "could not read compiled SPIR-V");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. spirv-val under the engine's relaxed block-layout flags.
|
||||
std::string validate = "spirv-val \"" + spv.string()
|
||||
+ "\" --relax-block-layout --scalar-block-layout --target-env vulkan1.4";
|
||||
if (RunCommand(validate) != 0) {
|
||||
std::println(std::cerr, "spirv-val rejected {}", variant.file);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. Exactly one push-constant block.
|
||||
int pcVars = CountPushConstantVariables(words);
|
||||
if (pcVars != 1) {
|
||||
std::println(std::cerr, "expected exactly 1 push-constant variable, found {}", pcVars);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 4. Member offsets must match UIFusedHeader's layout exactly.
|
||||
const std::vector<std::uint32_t> expected = {0, 16, 32, 48, 64, 80, 96, 112};
|
||||
std::vector<std::uint32_t> offsets = PushConstantMemberOffsets(words);
|
||||
if (offsets != expected) {
|
||||
std::print(std::cerr, "push-constant member offsets mismatch — expected {{");
|
||||
for (auto o : expected) std::print(std::cerr, "{} ", o);
|
||||
std::print(std::cerr, "}}, got {{");
|
||||
for (auto o : offsets) std::print(std::cerr, "{} ", o);
|
||||
std::println(std::cerr, "}}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::println(std::cout,
|
||||
"ui-fused shader ok, {} build (push-constant vars: {}, member offsets pinned to UIFusedHeader, 128 bytes)",
|
||||
variant.label, pcVars);
|
||||
}
|
||||
|
||||
std::vector<std::uint32_t> words = ReadSpirv(spv);
|
||||
if (words.size() < 5) {
|
||||
std::println(std::cerr, "could not read compiled SPIR-V");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. spirv-val under the engine's relaxed block-layout flags.
|
||||
std::string validate = "spirv-val \"" + spv.string()
|
||||
+ "\" --relax-block-layout --scalar-block-layout --target-env vulkan1.4";
|
||||
if (RunCommand(validate) != 0) {
|
||||
std::println(std::cerr, "spirv-val rejected ui-fused.comp.spv");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. Exactly one push-constant block.
|
||||
int pcVars = CountPushConstantVariables(words);
|
||||
if (pcVars != 1) {
|
||||
std::println(std::cerr, "expected exactly 1 push-constant variable, found {}", pcVars);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 4. Member offsets must match UIFusedHeader's layout exactly.
|
||||
const std::vector<std::uint32_t> expected = {0, 16, 32, 48, 64, 80, 96, 112};
|
||||
std::vector<std::uint32_t> offsets = PushConstantMemberOffsets(words);
|
||||
if (offsets != expected) {
|
||||
std::print(std::cerr, "push-constant member offsets mismatch — expected {{");
|
||||
for (auto o : expected) std::print(std::cerr, "{} ", o);
|
||||
std::print(std::cerr, "}}, got {{");
|
||||
for (auto o : offsets) std::print(std::cerr, "{} ", o);
|
||||
std::println(std::cerr, "}}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::println(std::cout,
|
||||
"ui-fused shader ok (push-constant vars: {}, member offsets pinned to UIFusedHeader, 128 bytes)",
|
||||
pcVars);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue