perf(image): batch final mip-chain layout transition (#70)

The mip-generation upload path issued N+1 layout-transition barriers per
chain. The final blit's destination level is never read again, so its
dedicated TRANSFER_DST -> TRANSFER_SRC barrier was redundant: the level was
transitioned to SRC only to keep the final SRC -> consumer transition uniform.

Skip that last per-level barrier and fold the final level into the closing
transition, which now batches two VkImageMemoryBarrier entries
([0, mipLevels-1) from TRANSFER_SRC, the last level from TRANSFER_DST) into a
single vkCmdPipelineBarrier. One fewer barrier call per chain; the N-1
interleaved per-level barriers stay one-at-a-time since each is read by the
next blit. Applied to both the host-upload and GPU-decompress Update paths.

Adds the MipChainBarrierBatch regression test driving the pure barrier
builder (no GPU device needed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-16 18:30:44 +00:00
commit 518509aa0e
3 changed files with 202 additions and 4 deletions

View file

@ -415,6 +415,33 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
mc.GetInterfacesAndImplementations(ifaces, memImpls);
cfg.tests.push_back(std::move(memTest));
// Issue #70: ImageVulkan's mip-chain upload folds the never-read final
// blit destination into a single batched final-transition barrier
// instead of giving it a dedicated DST->SRC barrier. The barrier set is
// built by BuildMipChainFinalBarriers, pure CPU logic over mip count and
// layout, so this test drives it directly — no GPU device at runtime.
Test mipBarrierTest;
Configuration& mbc = mipBarrierTest.config;
mbc.path = cfg.path;
mbc.name = "MipChainBarrierBatch";
mbc.outputName = "MipChainBarrierBatch";
mbc.type = ConfigurationType::Executable;
mbc.target = cfg.target;
mbc.march = cfg.march;
mbc.mtune = cfg.mtune;
mbc.debug = cfg.debug;
mbc.sysroot = cfg.sysroot;
mbc.dependencies = cfg.dependencies;
mbc.externalDependencies = cfg.externalDependencies;
mbc.compileFlags = cfg.compileFlags;
mbc.linkFlags = cfg.linkFlags;
mbc.defines = cfg.defines;
mbc.cFiles = cfg.cFiles;
std::vector<fs::path> mipBarrierImpls(impls.begin(), impls.end());
mipBarrierImpls.emplace_back("tests/MipChainBarrierBatch/main");
mbc.GetInterfacesAndImplementations(ifaces, mipBarrierImpls);
cfg.tests.push_back(std::move(mipBarrierTest));
// Issue #47: the fused UI uber-kernel (shaders/ui-fused.comp.glsl) and
// its C++ push-constant mirror UIFusedHeader. Compiles the real shader
// with glslang, validates with spirv-val, and pins the push-constant