Direct3D evidence: MinGW cross-builds, Wine translators and native Windows

CNA snapshot 3b43bd86  ·  Deep Dives › Renderers  ·  source links pinned to 3b43bd86

✓

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 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Read from CMake registrations, wrapper scripts and the workflow file at c6dec91f and re-checked at 3b43bd86; no Wine session, Windows run or test was executed for this page, and no pass counts are claimed.

CNA has one Direct3D renderer, DIRECTX9, and on Windows its default renderer, SDL_GPU, runs on SDL’s Direct3D 12 GPU driver. Both are developed mostly on Linux: cross-compiled with MinGW-w64 and run under Wine, with Direct3D translated to Vulkan on a real GPU. This page explains that loop precisely: which translator serves which path, the gates that stop a silent fall-back to Wine’s own Direct3D, how the CTest registrations run, and what the native-Windows workflow adds. It is for anyone who reads a Direct3D test result, adds a Direct3D fixture, or has to decide how much a green Wine run proves.

One Direct3D identity, one development loop

DIRECTX9 is Windows-only: RendererSelection.cmake stops the configure step with a FATAL_ERROR unless CMAKE_SYSTEM_NAME is Windows, which means a native MSVC or MinGW build on Windows or a cross-build from Linux with cmake/toolchains/mingw-w64.cmake. SDL_GPU builds everywhere SDL3 does; on Windows SDL selects its Direct3D 12 GPU driver underneath it, and the same MinGW-w64 cross-build carries it into Wine. The step-by-step commands are in Tutorial 103; this page is about what the resulting runs mean.

PathStock shadersShared codeTranslator on the Linux loopWrapper script
DIRECTX9Microsoft's XNA stock-effect .fx sources compiled offline to SM2 bytecode (see DIRECTX9 fidelity)none: D3D9 has its own format and state tablesDXVK (Direct3D 9 to Vulkan)run-wine-dxvk9.sh
SDL_GPU on SDL’s Direct3D 12 driverthe renderer’s SPIR-V stock shaders, translated by SDL_shadercross, which Windows builds link by defaultnone: the Direct3D 12 driver is SDL’svkd3d-proton (Direct3D 12 to Vulkan)run-wine-vkd3d-headless.sh

DIRECTX9 creates render targets, MRT sets and occlusion queries through its native API; the query is created only when CreateQuery(D3DQUERYTYPE_OCCLUSION, nullptr) reports support (DirectX9Renderer.cpp). SDL_GPU has no occlusion queries on any driver, and its ShaderEffect is compiled out of a Windows build (evidence tiers). Capability answers are in the capability matrix.

What the cross-build adds

The DIRECTX9 examples directory registers tests only when DIRECTX9 is the configured renderer and CNA_BUILD_TESTS is on. Its cna_directx9_test macro (DirectX9 examples CMake) links every test executable statically against the MinGW C and C++ runtimes (-static-libgcc -static-libstdc++), adds -Wl,--allow-multiple-definition, and copies the MinGW and SDL runtime DLLs next to the executable (cna_copy_mingw_runtime, cna_copy_sdl_runtime in ThirdPartySDL.cmake), so a test .exe runs in a Wine prefix without an installed toolchain. When CMAKE_CROSSCOMPILING is true the registered command is the wrapper script with the executable as its argument; a native Windows build registers the executable itself. The SDL_GPU examples (SDL_GPU examples CMake) do the same with the vkd3d-proton wrapper.

The DIRECTX9 renderer’s own custom shaders (its PBR and skinned vertex-colour programs) are also produced by this loop, but offline: a small D3DCompile() tool is cross-built and run under Wine against Microsoft’s real d3dcompiler_47.dll, and the bytecode is checked in. verify_d3d_shaders_reproducible.sh rebuilds that tool, recompiles the unmodified sources and fails unless the result is byte-identical to what is checked in; it is a script a maintainer runs by hand, not part of any CMake target.

Two translators, separate prefixes

DIRECTX9 runs in the prefix named by CNA_D3D9_WINEPREFIX, which defaults to ~/.wine-cna-d3d11: a prefix prepared with dxvk-setup install, whose installer also redirects d3d9.dll to DXVK. Three registrations (DirectX9_ConstantTable, DirectX9_EffectRenderer and DirectX9_SpriteBatch_CustomEffect) pin a different prefix, ~/.wine-cna-d3d9-spike, because only it holds Microsoft's real d3dcompiler_47.dll: the default prefix has just Wine's builtin one, which cannot compile Shader Model 2/3 sources. The two effect tests open a device and keep the DXVK gate; the constant-table test never creates one and opts out. Which DXVK the prefix carries matters: CNA records that DirectX9_CompiledEffects hangs or aborts in DXVK 2.6.0’s device teardown and passes with DXVK 3.1.1 (handoff.md, section 5.3).

Cross-compiled SDL_GPU tests run through run-wine-vkd3d-headless.sh, which starts a private Xvfb server per invocation, selects SDL’s dummy video driver and its direct3d12 GPU driver, and hands the executable to run-wine-vkd3d.sh (vkd3d-proton in its own prefix, ~/.wine-cna-d3d12, overridable with CNA_D3D12_WINEPREFIX). Wine and vkd3d need a virtual X server to enumerate the Vulkan-backed DXGI adapter even though no swap chain is created.

Authenticity gates: proving the translator actually ran

If a DLL override is missing, Wine silently uses its own WineD3D implementation. The test still runs, still produces plausible pixels, and proves nothing about DXVK, let alone about Direct3D. The wrappers therefore inspect the run's own log before reporting success:

WrapperRequired log lineOn failureDeliberate opt-outs
run-wine-dxvk9.sh (DIRECTX9)a DXVK: <major>.<minor> line, with or without the v that official DXVK builds print before the versionexit status 3CNA_D3D9_SKIP_DXVK_GATE=1 for an executable that never opens a device (the pure mapping-table tests DirectX9_Common and DirectX9_ShaderDispatch, and DirectX9_ConstantTable); CNA_D3D9_ALLOW_WINED3D=1 for a deliberate one-off WineD3D diagnostic, never for normal runs
run-wine-vkd3d.sh, called by run-wine-vkd3d-headless.sh (SDL_GPU cross tests)vkd3d-proton - applicationVersion: x.y.zexit status 3CNA_D3D12_SKIP_VKD3D_GATE=1

No fixture exercises either gate with a stub wine, so a regression in a gate would show only as a run that fails or passes for the wrong reason; read the log line, not just the exit status.

⚠

A marker proves which translator handled the run. It does not prove Direct3D behaviour on a Windows driver: DXVK implements Direct3D 9 on top of one Vulkan driver, and on the DIRECTX9 loop even D3DCAPS9 is DXVK's synthesized capability set, not what an XNA-era Direct3D 9 driver would report. A vkd3d-proton run likewise proves SDL’s Direct3D 12 driver against vkd3d-proton, not against a Windows driver.

How CTest runs the Direct3D paths

  • DIRECTX9. A broad DirectX9_Smoke plus about sixty focused registrations in its examples CMake, all labelled DIRECTX9, including the oracle corpus diff D3D9_XNA_Diff. CNA records that the whole suite passed 60 of 60 twice under DXVK 3.1.1, and 59 of 59 without DirectX9_CompiledEffects under DXVK 2.6.0, on 17 October 2026 (not re-run here).
  • SDL_GPU under vkd3d-proton. The cross-compiled SDL_GPU examples are registered with the headless vkd3d wrapper as their launcher, so the same pixel and lifecycle programs that run on SDL’s Vulkan driver on Linux run on its Direct3D 12 driver under Wine.

Registration is not execution. Report ctest -N and the executed results for the exact configuration; nothing was run for this page.

What the native Windows workflow adds

d3d-windows-ci.yml is a workflow_dispatch-only (manual) job on windows-latest with two legs. Both build with MSVC, a different compiler from the MinGW g++ every other Direct3D result comes from, and run with no Wine involved. The DIRECTX9 leg builds the renderer’s own CTest executables (not CnaTests) and runs ctest -L DIRECTX9 against the real Windows Direct3D 9 runtime. The SDL_GPU leg builds a focused smoke set and runs it through run-sdlgpu-smoke.sh with SDL’s windows video driver and direct3d12 GPU driver on whatever Direct3D 12 adapter the hosted runner offers, failing unless every program exits 0. The workflow’s own header scopes it as a native-runtime regression gate, not a substitute for swap-chain, device-lost or driver-parity verification on a machine with a display and a GPU driver, and calls the full native SDL_GPU Direct3D 12 qualification a separate campaign. CNA’s handoff records the SDL_GPU leg as not yet run and lists no current real-Windows hardware validation.

The POSIX-environment rule behind a Windows CnaTests

The native workflow builds the renderer’s own CTest executables, not CnaTests, and one long-standing obstacle to a Windows CnaTests is a rule with a checking script of its own. MinGW-w64, like MSVC, has neither setenv nor unsetenv, so a single call in any translation unit that reaches a Windows build stops CnaTests.exe from compiling. The repair that CNA’s checking script records replaced the call sites with System::Environment::SetEnvironmentVariable(name, value), where an empty value means unset. At this snapshot 52 files under modules/, tools/ and tests/ use that call.

Eleven new POSIX calls had appeared by 2026-08, so the rule was written down as a text check. check_no_posix_setenv.py scans every .cpp and .hpp under modules/, tools/ and tests/, skips comment lines and third-party trees, and fails on any setenv( or unsetenv( call. The script's point is to fail on a Linux build long before anyone attempts a Windows one. At this snapshot no CMake file and no workflow runs it, so it is a check a maintainer invokes by hand, not a CTest.

The script does not pass at this snapshot. Its pattern, applied read-only to the snapshot tree for this page, matches 5 calls in 3 files, all Storage test files. Because the script matches text, not targets, a red result says “a POSIX environment call exists somewhere in the tree”, and only reading the call sites shows whether a Windows build is affected.

What each tier proves

TierProvesDoes not prove
Source exists, cross-build linksthe code compiles for Windows with MinGW-w64 and the symbols resolveany behaviour
Gated Wine run with GPU readbackcall sequencing, resource lifetimes, shader compilation and pixel arithmetic through DXVK (DIRECTX9) or vkd3d-proton (SDL_GPU) on one Vulkan driver; results come from GPU readback, not from an S_OKWindows driver behaviour, DXGI presentation and tearing, a genuine device loss or removal
XNA oracle (D3D9_XNA_Diff)byte-exact agreement with real XNA 4.0 on the committed scenes when the test passes, with both sides running on the same DXVK layer and GPU (CNA's records report all 39 matching; the last dated report covers 31 - see the oracle records)anything the scenes do not exercise; native Windows; CI
Manual native Windows workflowwhen run: an MSVC build, the DIRECTX9 CTest entries on the real Windows Direct3D 9 runtime, and an SDL_GPU device, swap chain, draw and read-back on SDL’s Direct3D 12 driver on the hosted runner’s adapterphysical display, multi-vendor drivers, device loss; nothing until a run is recorded

For DIRECTX9, the plan item for real Windows hardware is the only one marked as needing a human; its text calls the oracle re-run on Windows against real XNA the definitive result. For SDL_GPU, CNA’s handoff lists a native Windows run on Direct3D 12 as not done although SDL_GPU is the default renderer on Windows.

Read in this order

  1. run-wine-dxvk9.sh and run-wine-vkd3d.sh: the gates and prefix variables.
  2. d3d-windows-ci.yml: what the native job runs and what it disclaims.
  3. DirectX9 examples CMake and the oracle README: the fidelity renderer's own registrations; the method is on Using the XNA oracle as evidence.
  4. handoff.md, sections 2 and 5: the recorded Wine runs and the open Windows qualification.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Tests and validation
Test architecture: GPU tests