CNA-BUG-095: A GraphicsDevice constructor that fails after renderer resolution leaves the selection latched to the renderer it destroyed

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 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

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.

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

Known issues
Bug index