CNA-BUG-115: DIRECTX9 requests D3DPRESENT_INTERVAL_TWO without checking device support, and a Reset that rejects it leaves the device lost and retried every frame
Evidence basis: source-verified at the pinned commit; inferred from the source (the behaviour was not run); tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.
PresentInterval::Two becomes D3DPRESENT_INTERVAL_TWO without a caps or windowed-mode check; when Reset rejects it the renderer marks the device lost and its recovery retries the same Reset every frame, skipping Draw and Present, while still reporting Two.
- Identifier
CNA-BUG-115- Category
- Bug
- Subsystem
- Graphics & renderers
- Status
- Open
- Verified against
- CNA
3b43bd86(3b43bd8687ed46523d873f9bec4b32bd8481dcb5) - Severity
- Low (a triage suggestion, not a project priority)
- Evidence basis
- Inferred (probable): depends on a platform or toolchain step nobody executed
- Tests touching this area
- Yes: see Current tests
- Affected contract
- PresentationParameters.PresentationInterval = PresentInterval::Two on DIRECTX9
Expected behaviour
An unsupported interval should be clamped (and the applied value reported) or refused once.
Actual behaviour at TARGET
DirectX9Renderer.cpp's BuildPresentParameters switches on the interval with no caps check, so Two becomes D3DPRESENT_INTERVAL_TWO whatever the device or window mode. At construction a rejected value fails once and loudly (CreateDevice throws). After construction, SetSwapInterval (reached from GraphicsDevice::Reset and SetPresentationParameters) marks the presentation dirty; EnsureDeviceSize (run from TryBeginDrawEXT and Present) releases every D3DPOOL_DEFAULT resource and calls device_->Reset, and on failure keeps presentationDirty_, calls MarkDeviceLostFromScopedFailureEXT and logs ‘device lost, awaiting reset recovery’. The recovery path (PollDeviceLost, then PerformResetRecovery) rebuilds the same parameters, so the same Reset is attempted again on every draw gate. Direct3D 9 documents only DEFAULT, IMMEDIATE and ONE as valid for windowed swap chains, so a windowed device switched to Two after construction (for example by GraphicsDevice::Reset with new PresentationParameters; GraphicsDeviceManager only ever requests One or Immediate) can stay lost indefinitely: TryBeginDrawEXT refuses every frame, so Draw and Present are skipped while Update keeps running, with PresentationParameters still claiming Two.
Source locations
modules/renderers/directx9/src/DirectX9Renderer.cpp— BuildPresentParameters, SetSwapInterval, EnsureDeviceSize failure branch
Evidence
Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry. The windowed-mode restriction is Direct3D 9's documented behaviour, not re-checked on a device; whether Wine/DXVK reject it the same way was not observed.
Independent re-verification: Checked by reading the named sources at 3b43bd86; nothing was built or executed for this entry. The windowed-mode restriction is Direct3D 9's documented behaviour (XNA's PresentInterval documentation likewise guarantees only One and Immediate for windowed swap chains and tells callers to check capabilities for Two), not re-checked on a device; whether Wine/DXVK reject it the same way was not observed, so the retry loop is inferred rather than verified. No DIRECTX9 test requests PresentInterval::Two.
Re-verified at c6dec91f: BuildPresentParameters is unchanged; d0cea7036 made a failed EnsureDeviceSize Reset enter device-loss recovery immediately instead of logging ‘retrying next frame’, which changes the route of the retry but not the outcome. Not executed.
Focused reproduction
No focused reproduction is known. Nothing has been invented here; the evidence above is what exists.
Current tests
No DIRECTX9 test requests PresentInterval::Two.
Regression test
A DIRECTX9 test that requests Two in a window and asserts that Reset succeeds once (clamped to One, applied value written back) or fails once with a clear exception.
Blast radius
DIRECTX9 games that use PresentInterval::Two, especially windowed.
Workaround
Use PresentInterval::One with a fixed time step for 30 Hz pacing.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- Deep dives
- Direct3D presentation: present interval
- Known issues
- Bug index