perf(buffer): capacity-reuse in VulkanBuffer::Resize (#63)

Resize previously always destroyed + reallocated. It now reuses the
existing allocation in place when the new request still fits the created
capacity and the immutable-at-create properties match: usage flags are
fixed at create, and the chosen memory type must still satisfy the
required property flags. preferredPropertyFlags is a best-effort perf
hint and is intentionally not part of the guard. On reuse the buffer
handle, device address and mapped pointer are preserved; only `size`
shrinks to the new logical extent.

Tracks capacity and the created usage flags on VulkanBufferBase, set at
Create and carried through the move constructor.

Adds VulkanBufferResizeReuse, a device-free regression test that drives
the reuse guard directly and asserts the in-place path issues no Vulkan
call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-16 18:22:09 +00:00
commit b6d0ec5cae
3 changed files with 189 additions and 0 deletions

View file

@ -472,6 +472,35 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
fc.GetInterfacesAndImplementations(ifaces, flushImpls);
cfg.tests.push_back(std::move(flushTest));
// Issue #63: VulkanBuffer::Resize now reuses the existing allocation in
// place when a new request still fits the created capacity and the
// immutable-at-create properties match (usage flags fixed at create;
// chosen memory type still satisfies the required flags), instead of
// always destroying + reallocating. The reuse guard is pure logic over
// recorded fields, so this test stamps a fake handle + capacity/flags and
// verifies the in-place path issues no Vulkan call — no GPU device needed.
Test resizeTest;
Configuration& rc = resizeTest.config;
rc.path = cfg.path;
rc.name = "VulkanBufferResizeReuse";
rc.outputName = "VulkanBufferResizeReuse";
rc.type = ConfigurationType::Executable;
rc.target = cfg.target;
rc.march = cfg.march;
rc.mtune = cfg.mtune;
rc.debug = cfg.debug;
rc.sysroot = cfg.sysroot;
rc.dependencies = cfg.dependencies;
rc.externalDependencies = cfg.externalDependencies;
rc.compileFlags = cfg.compileFlags;
rc.linkFlags = cfg.linkFlags;
rc.defines = cfg.defines;
rc.cFiles = cfg.cFiles;
std::vector<fs::path> resizeImpls(impls.begin(), impls.end());
resizeImpls.emplace_back("tests/VulkanBufferResizeReuse/main");
rc.GetInterfacesAndImplementations(ifaces, resizeImpls);
cfg.tests.push_back(std::move(resizeTest));
// Issue #89: Device::PreferDirectDeviceWrite chooses the upload strategy
// for a CPU-written, GPU-read buffer — direct HOST_VISIBLE|DEVICE_LOCAL
// map+write on ReBAR/UMA vs. staged-into-pure-DEVICE_LOCAL on a small