perf(image): release static texture staging after upload (#114)
ImageVulkan kept its host-visible staging `buffer` (sized w*h, persistently mapped) alive for the whole life of the image and Destroy() never freed it, so static textures (e.g. Sponza albedo) pinned HOST_VISIBLE / small-BAR memory forever. Update now releases the staging via VulkanBuffer::DeferredClear() right after recording the buffer→image copy — the same fence-keyed deletion queue (#101/#102) the compressed Mesh path already uses — so it is freed once the upload submit's frame has cleared. A new `streamed` flag (set by FontAtlas) keeps the persistent map for images re-uploaded from a CPU-side staging buffer every frame; its uploads go through UpdateRegion, which never releases. Destroy now also frees any staging the image still owns, gated on a live handle so a released static texture can't double-free. Adds tests/ImageStagingRelease driving the real upload + readback on a headless device: asserts the staging is enqueued (not pinned), the image reads back byte-equal (released staging outlived the submit), the entry retires only after framesInFlight frames, and a streamed image keeps then frees its staging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
9d9f9d9d2c
commit
b4fa6e596e
4 changed files with 314 additions and 3 deletions
34
project.cpp
34
project.cpp
|
|
@ -651,6 +651,40 @@ extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> a
|
|||
msc.GetInterfacesAndImplementations(ifaces, meshStagingImpls);
|
||||
cfg.tests.push_back(std::move(meshStagingTest));
|
||||
|
||||
// Issue #114: a static ImageVulkan no longer pins its host-visible
|
||||
// staging `buffer` for the image's life — Update releases it via
|
||||
// DeferredClear() right after recording the buffer→image copy, so the
|
||||
// fence-keyed deletion queue (#101/#102) frees it once that submit's
|
||||
// frame clears, while a `streamed` image (the FontAtlas) keeps its
|
||||
// persistent map. Drives the real upload path on a headless device (no
|
||||
// swapchain/window — a buffer→image copy + readback only needs the queue
|
||||
// + command pool) and asserts the staging is enqueued (not pinned), the
|
||||
// image reads back byte-equal (the released staging outlived the submit),
|
||||
// the entry retires only after framesInFlight frames, and a streamed
|
||||
// image keeps + then frees its staging on Destroy. Shares the native
|
||||
// build settings.
|
||||
Test imageStagingTest;
|
||||
Configuration& isc = imageStagingTest.config;
|
||||
isc.path = cfg.path;
|
||||
isc.name = "ImageStagingRelease";
|
||||
isc.outputName = "ImageStagingRelease";
|
||||
isc.type = ConfigurationType::Executable;
|
||||
isc.target = cfg.target;
|
||||
isc.march = cfg.march;
|
||||
isc.mtune = cfg.mtune;
|
||||
isc.debug = cfg.debug;
|
||||
isc.sysroot = cfg.sysroot;
|
||||
isc.dependencies = cfg.dependencies;
|
||||
isc.externalDependencies = cfg.externalDependencies;
|
||||
isc.compileFlags = cfg.compileFlags;
|
||||
isc.linkFlags = cfg.linkFlags;
|
||||
isc.defines = cfg.defines;
|
||||
isc.cFiles = cfg.cFiles;
|
||||
std::vector<fs::path> imageStagingImpls(impls.begin(), impls.end());
|
||||
imageStagingImpls.emplace_back("tests/ImageStagingRelease/main");
|
||||
isc.GetInterfacesAndImplementations(ifaces, imageStagingImpls);
|
||||
cfg.tests.push_back(std::move(imageStagingTest));
|
||||
|
||||
// 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue