CNA-BUG-095: A GraphicsDevice constructor that fails after renderer resolution leaves the selection latched to the renderer it destroyed
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 GraphicsDevice constructor latches the selection inside resolveRenderer(), then updates the viewport and pushes default states; if one of those throws, the catch block destroys the renderer without unlatching, so SetPreferred is refused.
- Identifier
CNA-BUG-095- 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
- GraphicsDevice construction failure path and CNA::GraphicsRendererSelection::SetPreferred / IsLatched / GetActive
Expected behaviour
The design stated in GraphicsDevice::resolveRenderer (plan RTR-P5-18): a construction that throws must not freeze the selection, otherwise "a game that caught the error could not then try a different configuration -- and GetActive() would report a renderer that had never been created". GraphicsRendererFallbackTest.AFailedResolutionDoesNotLatch and RecoveringAfterAFailedResolutionActuallyWorks pin this for a resolution that created nothing.
Actual behaviour at TARGET
The constructor's try block calls resolveRenderer(), which ends with GraphicsRendererSelectionAccessEXT::Latch, and then UpdateViewportFromWindow(), setBlendStateProperty(BlendState::Opaque), setDepthStencilStateProperty(DepthStencilState::Default) (when the renderer has depth/stencil) and setRasterizerStateProperty(RasterizerState::CullCounterClockwise). Its catch (...) calls destroyNativeResources(), releases the video subsystem and rethrows; nothing resets the latch (only the test helper ResetForTestingEXT does). If any of those four steps throws, the renderer is gone, IsLatched() stays true, GetActive() names the destroyed renderer, and SetPreferred or SetFallbackChain throw InvalidOperationException, so the game cannot retry with another renderer in the same process.
Source locations
modules/graphics/src/Xna/GraphicsDevice.cpp— GraphicsDevice constructor try/catch (destroyNativeResources, no unlatch) and GraphicsDevice::resolveRenderer (Latch at the end)modules/core/src/GraphicsRendererSelection.cpp— GraphicsRendererSelectionAccessEXT::Latch; ResetForTestingEXT is the only unlatchmodules/graphics/tests/CNA/GraphicsRendererFallbackTests.cpp— AFailedResolutionDoesNotLatch covers only failures before a renderer exists
Evidence
Checked by reading at 009d40f5; not executed. The missing unlatch follows mechanically from the code; whether any current renderer can throw from those state pushes or the viewport update on a freshly created device was not established, so the window may be narrow. No test injects a failure after resolution.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
GraphicsRendererFallbackTests.cpp and MultiRendererFallbackTests.cpp cover failures during resolution (for example through CNA_DEBUG_FAIL_RENDERER_INIT), not after it.
Regression test
A fallback test with a test renderer whose state application throws after creation, asserting afterwards that IsLatched() is false and SetPreferred is accepted; the fix is to unlatch (and clear the active renderer) in the constructor's catch block when this construction performed the latch.
Blast radius
Games that catch a failed GraphicsDevice construction and retry with another renderer, and multi-renderer test fixtures that construct devices repeatedly in one process. Successful constructions and failures during resolution are unaffected.
Workaround
Choose the renderer before the first device and treat a failed construction as fatal for the process.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Runtime renderer selection: fallback
- Known issues
- Bug index