CNA-BUG-097: RenderPipeline::releaseDeviceResourcesEXT() keeps the Bloom, SSAO and volumetric-fog target pools, and the memory estimate never counts them

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

The DeviceReset handler documented to drop every target the pipeline owns resets only the scene target and the chain's pool; the private pools of BloomPass, SsaoPass and VolumetricFogPass survive, and getGpuMemoryEstimateBytes() excludes them.

Identifier
CNA-BUG-097
Category
Bug
Subsystem
Graphics & renderers
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Low (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
CNA::Graphics::RenderPipeline::releaseDeviceResourcesEXT() and getGpuMemoryEstimateBytes() / FrameStatistics::gpuMemoryEstimateBytes (CNAEXT)

Expected behaviour

RenderPipeline.hpp: releaseDeviceResourcesEXT() "Drops every target the pipeline owns, so the next frame allocates fresh ones" and is called automatically on DeviceReset, because "after a context loss every GPU object the pipeline held names storage the driver has already destroyed"; getGpuMemoryEstimateBytes() "Returns an estimate of the GPU memory the pipeline's own targets occupy".

Actual behaviour at TARGET

releaseDeviceResourcesEXT() resets sceneTarget_ and chain_.getTargetPool() and zeroes the statistics. BloomPass, SsaoPass and VolumetricFogPass, all owned by the pipeline, allocate from private RenderTargetPool pool_ members that the reset path does not touch (resize() resets the Bloom and SSAO pools only; the fog volume has a fixed size). getGpuMemoryEstimateBytes() sums the chain pool and the scene target only, so a frame with bloom, SSAO or volumetric fog under-reports, and after a reset the estimate reads 0 while those targets are still allocated.

Source locations

Evidence

Checked by reading at 009d40f5; not executed. DeviceLossTest.EverySubsystemSurvivesAResetAndRendersAgain and MultiDeviceTest.AResetOnOneDeviceDoesNotDisturbTheOther assert getGpuMemoryEstimateBytes() == 0 after a reset; they pass because the estimate omits the pass pools, not because those pools were released. What a kept pooled target does after a real loss depends on the renderer and was not established: WebGPU's DebugRestoreContext rebuilds every registered live resource (RecreateAfterDeviceLossEXT), which would keep such targets usable there, whereas the pipeline's documentation assumes they are dead.

Focused reproduction

// Illustrative; not compiled or run for this entry. HiDef device on a renderer that supports the passes.
CNA::Graphics::RenderPipeline pipeline(device);
pipeline.resize(256, 256);
pipeline.getSettings().setBloomEnabled(true);
pipeline.begin(Color::Black);
pipeline.end();
std::size_t before = pipeline.getGpuMemoryEstimateBytes(); // excludes BloomPass's own targets
pipeline.releaseDeviceResourcesEXT();
std::size_t after = pipeline.getGpuMemoryEstimateBytes();  // 0, while BloomPass still holds them

Current tests

DeviceLifecycleTests.cpp (DeviceLossTest.*, MultiDeviceTest.*), PipelineDiagnosticsTests.cpp (PipelineDeviceResetTest.*) and the passes' own suites (BloomPassTests.cpp, SsaoPassTests.cpp, VolumetricFogPassTests.cpp). None observes the pass pools through the pipeline.

Regression test

Have releaseDeviceResourcesEXT() reset the Bloom, SSAO and volumetric-fog pools (adding a reset to VolumetricFogPass), include those pools in getGpuMemoryEstimateBytes(), and extend DeviceLossTest to enable bloom and fog and assert that the estimate before a reset exceeds the chain's alone and is 0 afterwards.

Blast radius

CNAEXT RenderPipeline users with bloom, SSAO or volumetric fog enabled: memory statistics read low, and after DeviceReset those three passes keep their previous targets. The reset half is reachable only on renderers that raise DeviceReset and can run these passes. XNA-only builds are unaffected.

Workaround

After a device reset, dispose and recreate the RenderPipeline rather than relying on its reset handler.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index