CNA-BUG-078: Game::PollEvents fires the renderer's test-only context-loss hooks on F9/F10 in every build
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::PollEventscallsGraphicsDevice_.GetRenderer().DebugSimulateContextLoss() for a pressed, non-repeatedKeyCode::F9andDebugRestoreContext()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): EasyGLon the desktop: F9 and F10 both print a line tostd::cerr, notifyMetaGLof a context loss, recreate the native context through the platform, reload the GL entry points and recreate every registered resource.EasyGLTextureRenderer::recreate_gl_resourcere-uploads level 0 from the CPU shadow; DXT1/DXT3/DXT5 textures are re-uploaded from their retained compressed blocks (UploadLevelfalls back tocompressedLevels_when it is given no pixels); a texture with no surviving shadow (context recovery disabled) is re-created as zeros.DirectX9, 11 and 12: F9 marks the device lost and reportsRendererDeviceEvent::Lost; theDirectX11 and 12 renderers then refuseCanBeginDrawEXT(), so the game stops drawing until F10 performs the reset.Direct2D: F9 recreates the complete device-resource domain.WebGPU: F9 throwsSystem::NotSupportedExceptionwhile aTextureCube,RenderTargetCube,Texture3D, occlusion query or customShaderEffectis alive; nothing inPollEvents, Tick or Run catches it, so natively it endsRun()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
modules/runtime/src/Game.cpp— Game::PollEvents, KeyEvent branchmodules/graphics/include/CNA/Internal/Renderers/Common/IGraphicsRenderer.hpp— DebugSimulateContextLoss/DebugRestoreContext defaults; RendererDeviceEvent comment calling the channel test-onlymodules/graphics/src/Xna/GraphicsDevice.cpp— isRendererListedIn comment naming the channel a test seammodules/renderers/easygl/src/EasyGLRenderer.cpp— EasyGLRenderer::DebugSimulateContextLoss / DebugRestoreContext; EasyGLTextureRenderer::recreate_gl_resourcemodules/renderers/directx11/src/DirectX11Renderer.cpp— DirectX11Renderer::DebugSimulateContextLoss / DebugRestoreContextmodules/renderers/webgpu/src/WebGPURenderer.cpp— WebGPURenderer::DebugSimulateContextLoss refusal (NotSupportedException)modules/runtime/tests/Microsoft/Xna/Framework/GameEventSemanticsGoldenTests.cpp— debug-hotkey-f9 scenarios in the golden event transcript
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.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Input: keyboard
- Architecture
- Runtime lifecycle: what one Tick does
- Maintainer workflow
- Debugging: black or corrupted frames
- Known issues
- Bug index