Direct3D presentation, swap interval, clears, viewport and scissor
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 c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Read from the renderer sources and fixture registrations at c1c316b9; no test was executed. Half-rate pacing, tearing and exclusive full screen are not verified by any located test.
The two Direct3D renderers share a name but not a presentation model. DIRECTX9 recreates its whole device state through Reset(), while DIRECTX11 presents through a DXGI flip-model swap chain and compute a logical presentation rectangle inside it. This page compares, renderer by renderer, what happens to the five presentation modes, to PresentInterval, to the back-buffer and depth formats and the full-screen flag, to viewport and scissor, to clears and to depth bias. It is for porters who rely on any of these on Windows and for maintainers deciding whether a difference is a bug or a boundary. The cross-renderer contract these rows belong to is on presentation and back-buffer readback and targets, clears and viewports.
Presentation modes: one rule set, two geometry implementations
PresentationMode is a CNA extension (NativeBackBuffer, FixedHeightDynamicWidth, Stretch, Letterbox, Overscan); GraphicsDeviceManager forwards its choice to the device and the device to the renderer before every Reset. What the renderer does with it differs:
DIRECTX11keeps drawing straight into the physical swap chain and maps the game's logical canvas into it. It calls the sharedComputeD3DPresentationGeometry(D3DPresentation.hpp):GetViewportSize()returns the logical size andGetDefaultViewportRect()the physical destination rectangle, andGraphicsDevicemaps every public viewport and scissor through that rectangle (see viewport and scissor).NativeBackBuffer(or no virtual size) keeps logical equal to physical;FixedHeightDynamicWidthkeeps the virtual height and widens the logical width to the window's aspect;Stretchfills the drawable with the virtual size;Letterboxscales uniformly by the smaller axis ratio and centres the result;Overscanby the larger ratio, cropping. There is no separate logical render target and no final composite pass: the scaling is the viewport transform itself.DIRECTX9applies the same five rules with its own geometry code rather than the shared helper:GetViewportSize()returns the logical size (the window's client size forNativeBackBufferor no virtual size, the widened width forFixedHeightDynamicWidth, the virtual size otherwise), andGetDefaultViewportRect()returns the centred, scaled destination forLetterboxandOverscanand the whole back buffer for the others. Its window-to-logical transforms use the same rectangle, and Direct3D 9's half-pixel correction stays in physical pixels (FULLSCREEN-002, with a resize and motion regression indirectx9_resize_state_test.cpp).
The shared fixture presentation_mode_contract_test.cpp (DX-217) states the renderer-neutral contract for all five modes and is registered for DIRECTX11 through the DirectX parity inventory. A related gap is on the input side: CNA's input-model reading finds that DIRECTX9 and DIRECTX11 implement the window-to-logical transforms but never register them for their window, so mouse coordinates are not mapped through a letterbox (logical coordinates).
Back-buffer readback follows the same geometry
A GetBackBufferData() rectangle is in the game's logical space. When the presentation is not the identity, DIRECTX11 samples the physical back buffer at the centre of each logical pixel (presentX + floor((x + 0.5) * scaleX), likewise for y) and zero-fill samples outside the resource; before this, a 16×16 logical back buffer in a window that Windows will not make narrower than about 120 pixels read back the letterbox bars. When logical and physical agree the copy is direct. A device with no back buffer throws NotSupportedException by name rather than returning a zero-filled "successful" frame.
One PresentInterval::Two, two implementations
GraphicsDevice converts PresentInterval to 0 (Immediate), 2 (Two) or 1 (Default, One) and passes it to SetSwapInterval.
| Renderer | Representation | When it applies | Failure behaviour |
|---|---|---|---|
DIRECTX9 | D3DPRESENT_PARAMETERS.PresentationInterval: INTERVAL_IMMEDIATE, INTERVAL_TWO or INTERVAL_ONE | immediately: a change marks presentation dirty and runs the same Reset() path as a resize, because Direct3D 9 has no per-present interval argument | a failed Reset() is logged, the previous size is restored, the dirty flag stays set and every later Present() retries; the renderer does not roll back the public PresentationParameters |
DIRECTX11 | swapInterval_, plus vsyncEnabled_ = interval > 0 | at the next Present(), which passes max(0, interval) as DXGI's sync interval, so 2 reaches DXGI as 2 | a failed Present() is checked for device removal and logged, not thrown |
Two cautions remain. Microsoft's Direct3D 9 contract allows INTERVAL_TWO only when D3DCAPS9::PresentationIntervals contains it, and a windowed device supports only default, immediate and one; DIRECTX9 checks neither, and an ordinary CNA game window is windowed, so requesting Two there can make Reset() fail into the retry loop above. On DIRECTX11 the tearing flag (DXGI_PRESENT_ALLOW_TEARING) is used only when tearing is supported, requested, the interval is 0 and the swap chain is not in exclusive full screen, which it never is. The shared swap_interval_forwarding_contract_test.cpp proves only that toggling SynchronizeWithVerticalRetrace reaches the renderer as interval 0 or 1 (it reads GetSwapIntervalEXT(); it never sets PresentInterval::Two and cannot see what DXGI receives, so 2 reaching DXGI rests on reading Present()); no located test measures half-rate pacing or the no-vsync tearing branch on either renderer, and DXVK's synthesized capability structure could not replace a real Windows check anyway. A stored PresentInterval::Two is not evidence of half-refresh pacing.
Earlier descriptions of the DXGI renderer said it collapses every interval to 0 or 1. At this snapshot Present() passes the stored interval itself; only the tearing decision uses the Boolean.
Back-buffer format, depth format and full screen
DIRECTX9maps the requested back-bufferSurfaceFormatto aD3DFORMAT, substitutingA8R8G8B8forColor'sA8B8G8R8, which Direct3D 9 rejects for a swap chain. Depth mapsNone,Depth16,Depth24andDepth24Stencil8to no attachment,D3DFMT_D16,D3DFMT_D24X8(a real depth-only format) andD3DFMT_D24S8, andWindowed = !IsFullScreen. A managerResetreachesUpdatePresentationFormatEXT(), which resets the native device at once rather than at the nextPresent(), so a game that clears depth on its very first frame sees the requested surface. The mapping is not a guarantee that a format is a legal display mode, and a failed reset can leave the old attachments active while public state has changed.DIRECTX11keeps the colour format fixed atR8G8B8A8_UNORM(reported asColor) and ignores the full-screen argument, but the default depth attachment now followsPresentationParameters.DepthStencilFormat:Noneallocates nothing andDepth24sharesD24_UNORM_S8_UINTstorage with its stencil kept out of reach, because DXGI has no 24-bit depth-only format. A depth-format change recreates the default surfaces immediately.d3d_presentation_format_contract_test.cppchecks that the reported and the native formats agree. It never callsSetFullscreenStateor passes a full-screen descriptor toCreateSwapChainForHwnd, and itsexclusiveFullscreen_stays false: an SDL full-screen window is not DXGI exclusive full screen.
Flip-model presentation unbinds the back buffer
The DXGI swap chain uses DXGI_SWAP_EFFECT_FLIP_DISCARD with two buffers, and a flip-model Present() unbinds the back buffer from the output merger. Clear() names its render-target views explicitly and never noticed; every draw used whatever OMSetRenderTargets last bound, which after a present was nothing. From the second frame on a game showed the right clear colour and none of its geometry, and no readback test caught it because every one read before its first present. DirectX11Renderer::Present() now rebinds exactly the tracked set, leaving the viewport and any game-bound target as they were.
Viewport and scissor: applied immediately on both
| Renderer | Viewport | Scissor |
|---|---|---|
DIRECTX9 | SetViewport immediately, through the checked wrapper | SetScissorRect immediately; D3DRS_SCISSORTESTENABLE from RasterizerState |
DIRECTX11 | RSSetViewports immediately | RSSetScissorRects immediately; enable bit in the rasterizer state object |
SpriteBatch derives its projection from the effective viewport on both, so sprite coordinates stay local to a sub-viewport instead of being transformed twice. An enabled zero-area scissor clips everything on DIRECTX11, while the Vulkan and GL families expand it to the full target; CNA's Deferred_Scissor fixture records that divergence per renderer rather than hiding it. When the stencil reference or blend factor changes, DIRECTX11 rebinds its current state object with the new value.
Evidence differs in strength. The Direct3D 9 plan calls its viewport and scissor "oracle-proven", but the oracle corpus has no viewport or scissor scene (see the DIRECTX9 oracle notes); the supportable claim is native implementation plus the shared DirectX9_Deferred_Viewport and DirectX9_Deferred_Scissor fixtures. DIRECTX11 registers ViewportState, Viewport_Subregion, Scissor, Deferred_Viewport, Deferred_Scissor, SpriteBatch_CustomViewport and RenderTarget_ViewportScissorReset from the shared inventory.
Clears: native bits, one public contract
DIRECTX11clears every bound render-target view and, only when a depth-stencil view is bound, selects the depth and stencil clear bits independently. Inside the renderer a depth or stencil clear with no view bound is a no-op (it guards on its bound view), but the publicGraphicsDevice::Clearnever lets such a request through: it throwsInvalidOperationExceptionfirst, as XNA 4.0 does (see clear masking).DIRECTX9buildsIDirect3DDevice9::Clearflags from the depth format of the currently bound attachment (activeDepthStencilFormatOrdinal_, updated by every successful target transition), so a bound target without depth never receives a depth-clear bit and one without stencil never receives a stencil bit. Earlier descriptions said the flags consulted the back buffer's presentation format; that is no longer the code.
Above the renderer, GraphicsDevice derives depth and stencil availability from the bound 2D or cube attachment itself, and the ordered-clear fixture (GraphicsDevice_OrderedClear) is registered for both, the clear-overload fixture (ClearOverloads) for DIRECTX11. The public masking rules are on clear masking.
Depth bias: floats on Direct3D 9, integers on DXGI
XNA's RasterizerState.DepthBias is a normalised float. DIRECTX9 passes it and SlopeScaleDepthBias straight through as the bit pattern of D3DRS_DEPTHBIAS and D3DRS_SLOPESCALEDEPTHBIAS, which are floats. Direct3D 11 takes an integer count of the bound depth format's smallest resolvable step, so XnaDepthBiasToD3D (D3DStateMapping.cpp) scales by 65,535 for D16, 16,777,215 for D24S8 and 8,388,607 for 32-bit float depth, saturates at the int32 limits and maps NaN or an unknown format to 0.
Read in this order
D3DPresentation.hpp: the whole presentation geometry.DirectX11Renderer.cpp:Present,GetViewportSize,ReadBackbuffer,UpdatePresentationFormatEXT,SetSwapInterval.DirectX9Renderer.cpp:BuildPresentParameters,EnsureDeviceSizeand the clear family.
Related pages
The same subject is explained at several altitudes. These are the neighbouring pages at each one.
- User guide
- Graphics state: viewport and scissor · Graphics renderers: the Windows set · Tutorial 103: Direct3D on Windows
- Architecture
- Graphics architecture
- Tests and validation
- Test architecture: GPU tests