Tutorial 103: Direct3D 9 and 11 on Windows
What you’ll learn
- Cross-compiling
DIRECTX11(andDIRECTX9) from Linux with MinGW-w64. - Running and testing the resulting
.exeunder Wine with DXVK, including the guard that stops a silent fallback. - Exactly what Wine verification proves — and the five things it does not.
- What the manual native-Windows workflow has measured, and what each Direct3D renderer reports.
Before you start — Tutorial 72: Choosing a Renderer covers renderer selection and the platform gates this tutorial works within.
CNA has two native Direct3D renderers: DIRECTX11, its modern Windows renderer with a runtime HLSL shader route, and DIRECTX9, the XNA-fidelity renderer. Both are hard-gated to Windows, and the repository supplies a manual Linux cross-build/Wine validation route for them. In this snapshot that route is not run automatically, and the native-Windows MSVC workflow is manual-dispatch only, which is why this tutorial distinguishes what a manual result proves from continuous or native-hardware coverage.
The Windows gate is hard
Two renderers refuse to configure unless CMAKE_SYSTEM_NAME is Windows: DIRECTX9 and DIRECTX11. Attempting one on a native Linux or macOS configure is a FATAL_ERROR, not a warning, and the message points you straight at the cross-compilation toolchain.
CNA: DIRECTX11 renderer only builds when targeting Windows. Either build
natively on Windows, or cross-compile from Linux with
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake
The reason is simply that d3d9.h, d3d11.h and dxgi.h do not exist anywhere else. No CMake dependency is fetched for these renderers — the headers and import libraries (d3d9, d3d11, dxgi, d3dcompiler) come from the Windows SDK under MSVC, or from MinGW-w64's own copies when cross-compiling. On the platform side a Windows target uses the SDL3 platform, which reaches Windows through SDL’s own windows video driver and hands the renderer the window’s HWND; iOS accepts neither renderer.
Cross-compiling from Linux
sudo apt install mingw-w64
# Direct3D 11
cmake -S . -B build-dx11 \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_GRAPHICS_RENDERER=DIRECTX11 \
-DCNA_BUILD_TESTS=ON
cmake --build build-dx11 --parallel
# Direct3D 9 (the XNA-fidelity renderer)
cmake -S . -B build-dx9 \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_GRAPHICS_RENDERER=DIRECTX9 \
-DCNA_BUILD_TESTS=ON
cmake --build build-dx9 --parallel
The renderer names changed. D3D9 and D3D11 are not accepted CNA_GRAPHICS_RENDERER values any more — use DIRECTX9 and DIRECTX11. The CTest labels moved with them, so ctest -L D3D11 matches nothing; the label is DIRECTX11.
Example and test executables land at the root of the build directory (the Direct3D 11 smoke test is cna_test_directx11_smoke). Both renderers need cmake --build against a real target. CNA itself is an interface umbrella library with no sources and is not buildable; build CnaTests, a demo target, or the whole build directory.
The translation layer
A cross-compiled .exe runs on the development machine under Wine, with its Direct3D calls translated to Vulkan on the real GPU by DXVK:
| Renderer | Translator | Wrapper script |
|---|---|---|
DIRECTX11 | DXVK (Direct3D 11 to Vulkan) | scripts/run-wine-dxvk.sh |
DIRECTX9 | DXVK (Direct3D 9 to Vulkan) | scripts/run-wine-dxvk9.sh |
The rest of this tutorial walks through DIRECTX11; DIRECTX9 follows the same recipe and is covered under the XNA-fidelity section below. (SDL_GPU’s own Direct3D 12 driver has a separate Wine route through vkd3d-proton, scripts/run-wine-vkd3d-headless.sh; see Tutorial 131.)
# DXVK, for DIRECTX11
sudo apt-get install -y dxvk-wine64
WINEPREFIX=~/.wine-cna-d3d11 wineboot --init
WINEPREFIX=~/.wine-cna-d3d11 dxvk-setup install
scripts/run-wine-dxvk.sh build-dx11/cna_test_directx11_smoke.exe
The guard that stops a silent fallback
This is the detail worth copying into your own workflow. If the DLL override is not in place, Wine quietly falls back to its own built-in WineD3D implementation. Your test still runs, still produces plausible pixels, and proves nothing about the path you meant to exercise.
The wrapper scripts therefore assert on the run's own log output before reporting success: run-wine-dxvk.sh requires a DXVK: <version> marker. If the marker is missing, the script exits 3 rather than passing.
| Environment variable | Effect |
|---|---|
CNA_D3D11_WINEPREFIX | Override the Wine prefix the wrapper uses. |
CNA_D3D11_SKIP_DXVK_GATE=1 | Opt a binary out of the DXVK marker check — correct only for a test that legitimately never opens a D3D11 device, such as a pure mapping-table unit test. |
CTest wires the wrappers in automatically, so ctest --test-dir build-dx11 -L DIRECTX11 runs every registered test through the guarded path rather than bare wine.
What Wine verification proves, and what it does not
Running under DXVK on a real GPU is a genuinely strong test: it exercises real call sequencing, real resource lifetimes, real HLSL compilation and real pixel maths, and the results come back from actual GPU readback rather than from an API returning S_OK. It is far more than a compile check.
It is not real-Windows verification, and CNA does not claim otherwise. A Wine run does not establish any of the following:
- Real DXGI presentation and tearing behaviour.
- Real device-lost / device-removed recovery — the detection logic exists and is wired in, but a genuine removal cannot be triggered on the development loop, so the trigger path is unverified.
- WARP fallback.
- MSVC-versus-MinGW ABI parity.
- Multi-vendor driver behaviour — every Wine result comes from one AMD GPU through one translator.
There is one native-Windows MSVC workflow for the Direct3D renderers in this snapshot: d3d-windows-ci.yml (windows-latest) covers DIRECTX11. It is manual-dispatch only (workflow_dispatch) — it does not run on pushes or pull requests, and no workflow names DIRECTX9. The job builds with MSVC and runs the renderer’s CTest suite against the real Windows runtime DLLs, plus a compile of every checked-in HLSL shader with a real D3DCOMPILER_47.dll. CNA records a dispatched run of it in October 2026 passing all 300 CTest entries, with the window smoke test skipped because the hosted runner has no GPU, and all 58 shaders compiling; it is a native-runtime regression gate, not a physical-display or multi-vendor driver claim. Outside CI, CNA’s Windows plan records a native baseline on one physical machine: DIRECTX11 built with MSVC and run on a Windows 11 laptop with an Intel Iris Xe GPU (from 2026-09-25), including its classic test suite, the opt-in compiled-effect suite and three applications (cna-street, cna-car-simulator and cna-gltf-viewer). That is one machine and one GPU vendor. If you are shipping on Windows, budget for your own hardware verification pass.
DIRECTX11 creates its swap chain in its constructor and does not support the CNAEXT HeadlessEXT windowless device.
What SupportsCapability() says on the Direct3D renderers
At alpha.1 the Direct3D renderers relied on the permissive base answers. That changed in this snapshot: DIRECTX11 now has its own SupportsCapability() switch, with no default arm, so an unknown enumerator answers false. DIRECTX9 still inherits the base defaults wholesale. GraphicsCapability now has 19 members (14 at alpha.1).
| Capability | DIRECTX11 | DIRECTX9 |
|---|---|---|
ThreeD, DepthStencilBuffer, StencilBuffer, AnisotropicFiltering, WireFrame, OcclusionQuery, CustomEffects, Texture3D, Instancing, AdditiveBlending | true | true (inherited default) |
MultiStreamVertexInput | true | false |
MultipleRenderTargets | true | true, bounded by the graphics profile (Reach 1, HiDef 4) |
MultiSampleAntiAliasing | true when the device accepts 4x for RGBA8 (probed) | true |
CompiledEffects | only with -DCNA_DIRECTX11_COMPILED_EFFECTS=ON | only with -DCNA_DIRECTX9_COMPILED_EFFECTS=ON |
FloatRenderTargets, HalfFloatRenderTargets, HalfFloatTextureLinearFiltering | from DXGI format-support queries | false |
ComputeShaders, IndirectDraw | true at feature level 11_0 and above | false |
DIRECTX9 is the one Direct3D renderer that does not override SupportsCapability(): its answers are the inherited base defaults, so nothing in them was authored for that renderer. Do not design a feature-detection path around those answers — probe the real operation instead, or query the richer capability profile that Tutorial 101 describes.
The compiled-effects switches (both default OFF) enable XNA Effect Framework bytecode through MojoShader; a default configure reports CompiledEffects as false on both. Positive answers are still a hint rather than proof that a particular draw path executes, and the newest two are answers about the renderer alone: Direct3D 11 implements compute and indirect draws internally in this snapshot, but CNA has no public class through which game code dispatches compute work or creates an indirect-argument buffer. For a richer, queryable picture — features, numeric limits and per-format usage — use the RendererCapabilityProfile API described in Tutorial 101.
One concrete gap no capability answer reflects: binding a RenderTargetCube face inside a multiple-render-target set is unimplemented on DIRECTX9 (a single cube face bound alone works, and a multi-target set with a cube face throws); DIRECTX11 implements it in this snapshot. Occlusion queries are wired on both; on DIRECTX9 the query is created only when the device passes D3D9's own support probe, otherwise the renderer reports it unsupported.
If pixel-exactness is the goal: DIRECTX9
DIRECTX11 is a real, tested Direct3D implementation, but it is not the fidelity renderer. CNA's XNA oracle corpus is 39 scenes (256×256, HiDef) with reference PNGs captured from the genuine Microsoft XNA 4.0 runtime — run under Wine with DXVK on Linux, not on native Windows. DIRECTX9 is the only renderer recorded in the repository as matching all 39 at tolerance 0: the CTest D3D9_XNA_Diff renders every scene through a cross-built DIRECTX9 binary under Wine + DXVK and fails on any single-channel difference. The last consolidated dated divergence report covers 31 scenes (later per-scene notes record the rest), the run has not been repeated on native Windows hardware, and none of this runs in CI. That is a statement about DIRECTX9 over DXVK, not about Direct3D 9 on every native driver.
Pick DIRECTX11 for a modern Windows path with a runtime HLSL shader route; pick DIRECTX9 when bit-identical XNA 4.0 output is the requirement. For a 2D game, the Windows default SDL_RENDERER is the other choice; both Direct3D renderers draw SpriteBatch 2D as well.
Where to go next
- Tutorial 72: Choosing a Renderer
- Tutorial 107: CPU-only renderers
- Tutorial 101: Querying renderer capabilities
- Platform support
- The Windows renderers in the reference
- Building CNA
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Direct3D evidence: MinGW cross-builds, Wine translators and native Windows — How DIRECTX9 and DIRECTX11 are built and tested: MinGW cross-builds, DXVK gates and separate Wine prefixes, the shared parity inventory, the manual Windows job and what each evidence tier proves.
- Direct3D presentation, swap interval, clears, viewport and scissor — How DIRECTX9 and DIRECTX11 treat presentation modes, PresentInterval, back-buffer and depth formats, full screen, flip-model rebinding, viewport, scissor, clears and depth bias.
- DIRECTX11 internals: lifetimes, recovery, readback and target finalisation — DIRECTX11 device creation, the three lifetime groups and device recovery, compute and indirect draws, the HeadlessEXT refusal, back-buffer readback, MRT and cube finalisation and render-target usage.
- DIRECTX9: stock-effect bytecode, device lifecycle and oracle findings — How the XNA-fidelity renderer compiles Microsoft's stock effects, enforces GraphicsProfile from D3DCAPS9, recovers lost devices, handles targets and why its sprite projection is what it is.
- Windows from Linux: MinGW cross-builds, runtime staging and Wine evidence — The MinGW-w64 route from Linux to a runnable Windows test: toolchain, target-built SDL, DLL staging, CTest emulators, which Wine runtime owns each renderer, prefix hygiene and evidence tiers.