CNA-BUG-078: Game::PollEvents fires the renderer's test-only context-loss hooks on F9/F10 in every build

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.

A non-repeated F9 or F10 press in any CNA game calls the renderer's DebugSimulateContextLoss() or DebugRestoreContext(), a channel CNA itself describes as a test seam, with no build, option or opt-out guard.

Identifier
CNA-BUG-078
Category
Bug
Subsystem
Core & runtime
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (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
Microsoft::Xna::Framework::Game::Run() / Tick() (through the private PollEvents); CNA::Internal::Renderers::IGraphicsRenderer::DebugSimulateContextLoss() and DebugRestoreContext()

Expected behaviour

XNA's Game reserves no keys: a key press only reaches the game's input state. CNA's own sources call the DebugSimulateContextLoss()/DebugRestoreContext() channel “one-directional and test-only” (the RendererDeviceEvent comment in IGraphicsRenderer.hpp) and “a deliberate, named test seam” (GraphicsDevice.cpp), and the DirectX 11 header documents the hook as entering “the deterministic lost-device state used by lifecycle tests”. A shipped game should therefore not reach it from a player's key press, or only through an explicit debug opt-in.

Actual behaviour at TARGET

  • After passing each event to PlatformInputBridge::ProcessEvent, Game::PollEvents calls GraphicsDevice_.GetRenderer().DebugSimulateContextLoss() for a pressed, non-repeated KeyCode::F9 and DebugRestoreContext() for F10, unconditionally (no build, option or opt-out guard). The game still sees the key, and the framework action happens as well. What the press does depends on the renderer (read at 009d40f5):
  • EasyGL on the desktop: F9 and F10 both print a line to std::cerr, notify MetaGL of a context loss, recreate the native context through the platform, reload the GL entry points and recreate every registered resource. EasyGLTextureRenderer::recreate_gl_resource re-uploads level 0 from the CPU shadow; DXT1/DXT3/DXT5 textures are re-uploaded from their retained compressed blocks (UploadLevel falls back to compressedLevels_ when it is given no pixels); a texture with no surviving shadow (context recovery disabled) is re-created as zeros.
  • DirectX 9, 11 and 12: F9 marks the device lost and reports RendererDeviceEvent::Lost; the DirectX 11 and 12 renderers then refuse CanBeginDrawEXT(), so the game stops drawing until F10 performs the reset.
  • Direct2D: F9 recreates the complete device-resource domain.
  • WebGPU: F9 throws System::NotSupportedException while a TextureCube, RenderTargetCube, Texture3D, occlusion query or custom ShaderEffect is alive; nothing in PollEvents, Tick or Run catches it, so natively it ends Run() and in the browser loop it is logged and the loop stops.
  • GDI overrides both hooks with empty bodies; the remaining renderers use the no-op defaults.

Source locations

Evidence

Checked by reading Game.cpp (PollEvents) and every override of the two hooks at 009d40f5 (EasyGL, DirectX 9/11/12, Direct2D, WebGPU, GDI). GameEventSemanticsGoldenTests.cpp records a debug-hotkey-f9 scenario, so the key handling is known and pinned as observable behaviour; nothing documents it as a user-facing feature. Not executed.

Uncertain: the exact visible effect on each Direct3D family (whether a game notices beyond the paused drawing) and whether any CNA sample binds F9 or F10.

Independently observed as a separate finding (merged): A non-repeated F9 or F10 press in any CNA game calls the renderer's DebugSimulateContextLoss() or DebugRestoreContext(), which CNA describes as a test seam, with no build guard or opt-out.

Independently observed as a separate finding (merged): Any F9 press calls DebugSimulateContextLoss and any F10 press DebugRestoreContext on the active renderer, in release builds too, and games cannot turn it off.

Independent re-verification: Checked by reading Game.cpp (PollEvents) and every override of the two hooks at 009d40f5 (EasyGL, DirectX 9/11/12, Direct2D, WebGPU, GDI). The earlier statement that DXT textures come back blank on EasyGL is withdrawn: UploadLevel takes the retained compressed blocks from compressedLevels_ when recreate_gl_resource passes no pixels, so the reproduction's 'DXT texture is re-created without data' does not occur; the visible EasyGL effect is the two stderr lines and a full context recreation. The golden transcript records a debug-hotkey-f9 scenario, so the handler is known and pinned as observable behaviour; nothing documents it as a user-facing feature. Not executed.

Focused reproduction

Illustrative: any game on an EasyGL desktop profile. Run it with at least one DXT-compressed texture loaded and press F9; the standard error stream receives “[CNA] Simulating desktop GL context loss + immediate recreate” and the DXT texture is re-created without data. On WebGPU, create a TextureCube and press F9: Run() ends with System::NotSupportedException.

Current tests

The golden transcript (platform-event-semantics.txt) runs the F9 press and the F9 repeat through a real Game::RunOneFrame() and captures public state only; renderer-side recovery tests (for example webgpu_context_recovery_test, easygl_gltf_context_loss_test) call the hooks directly. No test asserts that an ordinary key press leaves the renderer alone.

Regression test

A runtime test with a renderer double that counts DebugSimulateContextLoss() calls: feed a non-repeated F9 KeyEvent through GameTestPeer::PollEvents and expect zero calls unless the chosen opt-in (a build option or a CNAEXT property) is enabled. Update the golden transcript accordingly.

Blast radius

Every Game on every platform that delivers key events, on the EasyGL, DirectX 9/11/12, Direct2D and WebGPU renderer families. Games that use F9 or F10 as gameplay keys (quick-save and quick-load are common bindings) trigger it on every press. Headless, software and other renderers with the default no-op hooks are unaffected, as are games driven without PollEvents.

Workaround

No workaround is known.

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

User guide
Input: keyboard
Known issues
Bug index