Tutorial 103: Direct3D 11 and 12 on Windows
What you’ll learn
- Cross-compiling
DIRECTX11andDIRECTX12from Linux with MinGW-w64. - Running and testing the resulting
.exeunder Wine with DXVK and vkd3d-proton, including the guards that stop a silent fallback. - Exactly what Wine verification proves — and the five things it does not.
- The Direct3D 12 presentation split, and why the routine test suite renders off-screen.
Before you start — Tutorial 72: Choosing a Renderer covers renderer selection and the platform gates this tutorial works within.
CNA has two native modern Direct3D renderers, DIRECTX11 and DIRECTX12. Both are hard-gated to Windows, and both are developed and routinely verified on Linux. That combination is unusual enough to be worth explaining properly, because it determines what the results mean.
The Windows gate is hard
Fourteen renderers refuse to configure unless CMAKE_SYSTEM_NAME is Windows. 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 d3d11.h, d3d12.h and dxgi.h do not exist anywhere else. No CMake dependency is fetched for either renderer — the headers and import libraries come from the Windows SDK under MSVC, or from MinGW-w64's own copies when cross-compiling.
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 -j3
# Direct3D 12
cmake -S . -B build-dx12 \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake \
-DCNA_GRAPHICS_RENDERER=DIRECTX12 \
-DCNA_BUILD_TESTS=ON
cmake --build build-dx12 -j3
The renderer names changed. D3D9, D3D11 and D3D12 are not accepted CNA_GRAPHICS_RENDERER values any more — use DIRECTX9, DIRECTX11 and DIRECTX12. The CTest labels moved with them, so ctest -L D3D11 matches nothing; the label is DIRECTX11.
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.
Two different translation layers
A cross-compiled .exe runs on the development machine under Wine, with its Direct3D calls translated to Vulkan on the real GPU. The two renderers need different translators, and their Wine prefixes do not interchange:
| Renderer | Translator | Wrapper script |
|---|---|---|
DIRECTX11 | DXVK (Direct3D 9/10/11 to Vulkan) | scripts/run-wine-dxvk.sh |
DIRECTX12 | vkd3d-proton (Direct3D 12 to Vulkan) | scripts/run-wine-vkd3d.sh |
# 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/examples/directx11_smoke_test.exe
# vkd3d-proton, for DIRECTX12 -- a separate, dedicated prefix
WINEPREFIX=~/.wine-cna-d3d12 wineboot --init
# copy vkd3d-proton's d3d12.dll / d3d12core.dll into the prefix, then:
WINEPREFIX=~/.wine-cna-d3d12 wine reg add \
'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d12 /d native /f
WINEPREFIX=~/.wine-cna-d3d12 wine reg add \
'HKEY_CURRENT_USER\Software\Wine\DllOverrides' /v d3d12core /d native /f
scripts/run-wine-vkd3d.sh build-dx12/examples/directx12_smoke_test.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.
Both wrapper scripts therefore assert on the run's own log output before reporting success: run-wine-dxvk.sh requires a DXVK: <version> marker, and run-wine-vkd3d.sh requires a vkd3d-proton - applicationVersion: <version> line. If the marker is missing, the script exits 3 rather than passing.
| Environment variable | Effect |
|---|---|
CNA_D3D11_WINEPREFIX / CNA_D3D12_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. |
CNA_D3D12_SKIP_VKD3D_GATE=1 | The same opt-out for vkd3d-proton. |
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. The following are all explicitly open in CNA's own tracking, for both renderers:
- 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 result comes from one AMD GPU through one translator.
There is a Windows MSVC CI workflow (d3d-windows-ci.yml, windows-latest) covering DIRECTX11, DIRECTX12 and DIRECT2D, but it is manual-dispatch only — it does not run on pushes or pull requests. If you are shipping on Windows, budget for your own hardware verification pass.
The Direct3D 12 presentation split
DIRECTX12 carries one extra wrinkle worth knowing before you debug it. Swap-chain creation crashes under plain Wine: a null-pointer read inside Wine's own dxgi.dll, caused by an architecture mismatch between the system dxgi.dll and the separately overridden vkd3d-proton d3d12.dll. It is not a CNA bug and it reproduces from a raw spike with no CNA code involved.
A properly Proton-managed launch gives vkd3d-proton the matched DLL pair it expects, and both swap-chain creation and real Present() work through a live window:
scripts/run-proton-vkd3d.sh build-dx12/examples/directx12_swapchain_diag.exe
Because Proton's bootstrap launch is heavy, the routine DIRECTX12 test suite deliberately renders off-screen instead: it binds a real render target and reads pixels back through a readback heap, never touching the swap chain. So use the ordinary CTest path for pixel correctness, and the Proton script only when you genuinely need a window on screen.
The off-screen route is available to your own code too. PresentationParameters exposes a CNAEXT HeadlessEXT flag that creates a windowless GraphicsDevice, which is what lets several D3D tests exercise the public XNA API with no window at all.
A capability warning specific to these two
DIRECTX11 and DIRECTX12 are two of the four renderers that do not override SupportsCapability() at all. Every capability query on them returns the inherited default, which is true for eleven of the thirteen members. Do not design a feature-detection path around those answers — probe the real operation instead. Tutorial 101 shows how.
Two concrete gaps neither answer reflects: occlusion queries are wired and pixel-tested on DIRECTX11 and DIRECTX12 but are not built at all on DIRECTX9, and binding a RenderTargetCube face inside a multiple-render-target set is unimplemented on all three.
If pixel-exactness is the goal, this is the wrong page
Both renderers here are real, tested Direct3D implementations. Neither is the fidelity renderer. CNA's XNA oracle corpus is 39 scenes captured from the genuine Microsoft XNA 4.0 runtime, and DIRECTX9 is the only renderer that matches all 39 at tolerance 0. Pick DIRECTX11/DIRECTX12 for a modern Windows path with a runtime HLSL shader route; pick DIRECTX9 when bit-identical XNA 4.0 output is the requirement.
Where to go next
- Tutorial 104: The historical DirectX ladder —
DIRECTX1throughDIRECTX10 - Tutorial 101: Querying renderer capabilities
- Platform support
- Building CNA