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

CNA snapshot c1c316b9  ·  Deep Dives › Renderers  ·  source links pinned to c1c316b9

✓

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 CMake registrations, wrapper scripts and the workflow file at c1c316b9; no Wine session, Windows run or test was executed for this page, and no pass counts are claimed.

CNA's two Direct3D renderers, DIRECTX9 and DIRECTX11, 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 renderer, the gates that stop a silent fall-back to Wine's own Direct3D, how the CTest registrations run each renderer, what the shared DirectX parity inventory contains, 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.

Two identities, one development loop

Both identities are 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. The step-by-step commands are in Tutorial 103; this page is about what the resulting runs mean.

IdentityStock 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
DIRECTX11one shared Shader Model 5 DXBC package (hlsl_shaders.hpp)common/d3d (D3DCommon)DXVK (Direct3D 11 to Vulkan)run-wine-dxvk.sh

Both create render targets, MRT sets and occlusion queries through their native APIs (on DIRECTX9 the query is created only when CreateQuery(D3DQUERYTYPE_OCCLUSION, nullptr) reports support). Their capability answers are in the capability matrix.

What the cross-build adds

Each family's examples directory registers tests only when that identity is the configured renderer and CNA_BUILD_TESTS is on. The cna_directx11_test macro (DirectX11 examples CMake) links every test executable statically against the MinGW C and C++ runtimes (-static-libgcc -static-libstdc++), add -Wl,--allow-multiple-definition, and copy 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 shipped stock shaders are also produced by this loop, but offline: compile_shaders_hlsl.py cross-builds a tiny D3DCompile() tool, runs it once per .hlsl file through the DXVK wrapper and embeds the DXBC as C++ arrays. The script is not part of any CMake target; it is rerun by hand after a shader edit, and the SHADERS list inside it (every entry compiled as vs_5_0 or ps_5_0) is the authority for what ships.

One translator, deliberately separate prefixes

DIRECTX11 runs in the prefix ~/.wine-cna-d3d11 (override with CNA_D3D11_WINEPREFIX) prepared with dxvk-setup install. DIRECTX9 uses the same physical prefix by default, because DXVK's installer also redirects d3d9.dll, but reads its own variable, CNA_D3D9_WINEPREFIX, so the two renderers' test infrastructure never share an environment contract by accident. Three DIRECTX9 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.

One more wrapper serves a renderer that is not a Direct3D identity: cross-compiled SDL_GPU tests, whose GPU driver on Windows is SDL’s own Direct3D 12 driver, run through run-wine-vkd3d-headless.sh, which starts a private Xvfb server per invocation and hands the executable to run-wine-vkd3d.sh (vkd3d-proton in its own prefix, ~/.wine-cna-d3d12), because 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-dxvk.sh (DIRECTX11)DXVK: v2.6 or DXVK: 2.6.0 (official and packaged builds print different forms; both are accepted)exit status 3CNA_D3D11_SKIP_DXVK_GATE=1 for an executable that never opens a device (the pure mapping-table test DirectX11_Common); CNA_D3D11_ALLOW_WINED3D=1 for a deliberate one-off WineD3D diagnostic, never for normal runs
run-wine-dxvk9.sh (DIRECTX9)a DXVK: <major>.<minor> line (the pattern has no optional v, so the official DXVK: v2.6 spelling that the DIRECTX11 wrapper accepts would be reported as a missing marker)exit status 3CNA_D3D9_SKIP_DXVK_GATE=1, CNA_D3D9_ALLOW_WINED3D=1
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

The DIRECTX11 wrapper's DXVK gate is itself tested without Wine (the DIRECTX9 and vkd3d wrappers carry their own copies of the gate, and no fixture exercises them): CTest DirectX11_DxvkGate, registered only in a DIRECTX11 tree, runs test-run-wine-dxvk-gate.sh, which puts a stub wine on PATH that prints each marker form (and none) and checks the wrapper's verdict, so a regression in the gate cannot hide behind a green suite. CNA_D3D11_VIRTUAL_DESKTOP (a Wine desktop specification such as CNA,1280x1024) runs the executable inside Wine's own desktop for hosts where a plain Xvfb screen is not exposed as a Windows display; a batch trampoline returns the viewer's stdout and, for an ordinary run, its exact exit status. A --capture run is judged differently: a newly written, size-stable PNG counts as success (exit 0) whatever the viewer returned, a cna-gltf-viewer: diagnostic line gives exit 1, and no completion within 30 seconds gives 124.

⚠

A marker proves which translator handled the run. It does not prove Direct3D behaviour on a Windows driver: DXVK implements Direct3D 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.

How CTest runs each renderer

  • Renderer-local programs. DirectX11_Smoke was narrowed (DX-243) to native integration that the renderer-neutral corpus cannot observe: native cache identity and keying, immediate-context binds and device recreation. Public pixels, resources, effects, queries, presentation and state semantics moved into the parity inventory below. The older, much larger smoke executable that earlier descriptions quote check counts for no longer exists in that form. DIRECTX9 keeps a broad DirectX9_Smoke plus about fifty focused registrations in its examples CMake, including D3D9_XNA_Diff.
  • The shared DirectX parity inventory. DirectXParityTests.cmake (DX-242) is one authoritative list of 264 fixtures, registered as DirectX11_<name> in a DIRECTX11 tree; the function refuses any other renderer. 176 of the sources are EasyGL fixtures and 84 are renderer-neutral graphics examples, so the Direct3D renderer is held to the same public-API assertions as the GL family; two are the renderer's own (Common, Pbr_VertexColor) and two are borrowed from the Vulkan and FNA3D examples. The macro refuses a fixture listed twice and fails the configure, naming the fixture and the path, when a cross-module source has moved.
  • Two fixtures need SDL. RealWindowResize and ViewportResetAfterResize reach into an SDL_Window to cross-check what CNA reported; with CNA_ENABLE_SDL=OFF they are skipped at configure time with a status message instead of breaking the build.

Registration is not execution. Report ctest -N and the executed results for the exact configuration; the site does not quote a pass count for these suites, and nothing was run for this page.

How a Direct3D pixel check is made discriminating

The Direct3D 11 bring-up built its 3D evidence as a ladder of GPU-readback checks, each designed so that the failure it targets cannot pass by accident. The ladder lived in the old, large DirectX11_Smoke; at this snapshot the same ideas are carried by the shared fixtures of the parity inventory. The design rules are what is worth reusing:

  • Change the pixel, then read it. Clear to a known colour, draw an opaque primitive over the same region, and require the new colour. A stale clear, an unbound shader or a draw that silently did nothing cannot pass; the first rung did this with the stride-16 VertexPositionColor path through both indexed and non-indexed calls, which exercises input layout, shaders, constant buffer, topology, rasterisation and readback together.
  • Separate the terms of a product. A textured draw that samples one known texel exactly, and a vertex colour multiplied through a white texture, isolate sampling from vertex-colour modulation.
  • Use exact controls where arithmetic is exact, inequalities where it is not. A lit draw's unlit control is byte-exact, while the lit result is only required to differ from both the unlit result and the clear colour: that proves the lighting branch ran without pretending a hand-derived floating-point GPU result is stable.
  • Choose inputs that falsify a specific mistake. Fog is drawn with the flag off (exact vertex colour) and at the fog end (exact fog colour); an environment-map check constrains the reflection to one cube face; a skinned check supplies an identity bone instead of an all-zero default matrix, which would collapse the geometry and hide a packing error. The shared BasicEffect_Fog, BasicEffect_LitVertexColor, EnvironmentMapEffect_Golden and SkinnedEffect_IdentityBones fixtures carry these cases to DIRECTX11.

A ladder like this supports the specific paths it names. It is not a licence to extrapolate that an untested state combination must also be correct.

What the native Windows workflow adds

d3d-windows-ci.yml is a workflow_dispatch-only (manual) job on windows-latest for DIRECTX11. It builds with MSVC, a different compiler from the MinGW g++ every other Direct3D result comes from, and runs ctest -L <renderer> against the real Windows runtime DLLs with no Wine involved. It also runs verify_hlsl_shaders_native_msvc.py, which rebuilds the shader tool with cl.exe and recompiles every checked-in .hlsl against a genuine D3DCOMPILER_47.dll. It deliberately does not byte-compare the output with the embedded DXBC, because a different compiler build may emit different but equivalent bytecode; the signal is that real Windows still accepts the HLSL. The workflow's own header scopes it as a native-runtime regression gate, not a physical-display, tearing, device-removal or driver-parity result, and states that the full CnaTests GoogleTest suite is not built on Windows because several test files call POSIX ::setenv(). CNA records one run of it on windows-latest on 2026-10-06 that passed all 300 registered CTest entries (not re-run here). No workflow names DIRECTX9.

The POSIX-environment rule behind a Windows CnaTests

The header of the native workflow gives POSIX ::setenv() in test files as the reason it does not build CnaTests, and that 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 recorded in docs/cnatests-mingw-setenv-proposal.md replaced the call sites with System::Environment::SetEnvironmentVariable(name, value), where an empty value means unset. At this snapshot 38 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 42 calls in 13 files: four Gamer Services example programs, eight Gamer Services test files, one network harness and one content-pipeline serializer test. Some of them sit beside a _WIN32 alternative or inside a POSIX-only block and others do not; 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, HLSL compilation and pixel arithmetic through DXVK on one Vulkan driver; results come from GPU readback, not from an S_OKWindows driver behaviour, DXGI presentation and tearing, a genuine device 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 workflowMSVC build, the DIRECTX11 CTest entries on real Windows runtime DLLs (300 of 300 passed in CNA’s recorded run of 2026-10-06), HLSL accepted by a real D3DCOMPILER_47.dllphysical display, multi-vendor drivers, device removal
One physical Windows 11 machine (CNA's recorded results)a native MSVC build of DIRECTX11 passing its classic suite on one Intel Iris Xe laptop with the debug layer, and three applications (cna-street, cna-car-simulator, cna-gltf-viewer) running on itother GPU vendors and drivers, DIRECTX9, repeatable CI

Even with those single-machine runs, CNA's own tracking keeps these items open for DIRECTX11: real DXGI presentation and tearing, a genuine device-removed recovery (the detection code and a deterministic simulated recovery exist; see device recovery), MSVC-versus-MinGW ABI parity beyond the manual job, and multi-vendor drivers. 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 the definitive result.

Debug layers and diagnostics

  • DIRECTX11 requests D3D11_CREATE_DEVICE_DEBUG in non-NDEBUG builds; CNA_D3D11_DEBUG_LAYER=1 forces it on in any build type and 0 forces it off. When the SDK debug component is missing (DXGI_ERROR_SDK_COMPONENT_MISSING) device creation retries without it. Informational messages are filtered at the info queue and the rest are drained into the shared debug-layer log after every Present().

Read in this order

  1. DirectXParityTests.cmake: the fixture inventory, its exceptions and the cross-compile environment rules.
  2. run-wine-dxvk.sh and run-wine-dxvk9.sh: the gates and prefix variables.
  3. d3d-windows-ci.yml: what the native job runs and what it disclaims.
  4. DirectX9 examples CMake and the oracle README: the fidelity renderer's own registrations; the method is on Using the XNA oracle as evidence.

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

Tests and validation
Test architecture: GPU tests