You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Direct3D 10 (ID3D10Device, real HLSL shaders) Graphics Backend — Implementation Plan
Status (2026-07-21): DONE.DX10-0 existence-gate spike complete; implementation phases
T1-T8 all complete, built, and verified: 10/10 D3D10-labeled CTests pass, no new cross-backend
regressions (GraphicsDeviceCapabilityTest shows only 2 pre-existing-style "out of this v1's
scope" gaps -- SupportsOcclusionQuery/SupportsCustomEffects -- NOT SupportsMultipleRenderTargets,
which D3D10 correctly closes unlike every DX1..DX8 backend). Three real bugs found only by
the full CTest suite, beyond the DX10-0 spike's own findings, are recorded in
docs/d3d10-backend.md §2: D3D10_BLEND_DESC shares one set of blend factors across all 8
targets (unlike D3D11_BLEND_DESC's per-target array -- a real API-shape difference, not a
mistake); a winding-order/back-face-culling bug where a 180°-rotated sprite was silently culled
(fixed with a dedicated cull-none rasterizer state for SpriteBatch's own draws); and a
SpriteBatch::Begin() ordering bug where this backend's own Begin() was resetting
transformMatrix_ to identity AFTER the caller's SetTransformMatrix() call had already set it
(not present in DX8's own Begin(), which this was modeled on -- an original mistake, not a
port defect). Also documented: RenderTargetUsage::DiscardContents (XNA's own default) auto-
clears ALL bound targets on an MRT bind based on only the FIRST target's own usage -- real,
documented XNA/FNA behavior, not a backend bug, found while writing the MRT CTest.
0. TL;DR
Direct3D 10 (2006) removed the fixed-function pipeline entirely — no SetVertexShader (rawFvfValue) trick like D3D8's, no D3DFVF_*, no fixed-function texture-stage states. Every draw
needs a real, compiled HLSL vertex+pixel shader pair (vs_4_0/ps_4_0 — D3D10's own shader-model
ceiling, no SM5.0). This backend is architecturally much closer to this project's own D3D11/D3D9
backends (both already compile real HLSL via D3DCompile) than to the DX1..DX8 CPU-transform
family — plan_dxold.md's row 10.
Delivered via Wine's own builtin d3d10.dll/d3d10_1.dll (thin wrappers; DXVK 2.6.0 ships no
d3d10.dll at all) forwarding to DXVK's real d3d10core.dll (native override) + DXVK's dxgi.dll
(native override — D3D10, unlike D3D8, genuinely needs a real DXGI swap chain).
1. What "Direct3D 10" concretely means for this backend
No fixed function at all. Every 2D (SpriteBatch) and 3D (DrawColoredPrimitives) draw is a
real HLSL shader pair, following this project's own D3DCompile-based precedent
(D3D9EffectBackend.cpp/D3D11EffectBackend.cpp), targeting vs_4_0/ps_4_0.
Real hardware blending, depth/stencil, rasterizer — as real state OBJECTS
(ID3D10BlendState/ID3D10DepthStencilState/ID3D10RasterizerState), not per-call render-state
setters like DX1..DX8's D3DRENDERSTATE_*/D3DRS_* — matching D3D11's own object-based
state model, a real architectural difference from the whole DX1..DX8 family.
Real MRT support (ID3D10Device::OMSetRenderTargets takes an array) — unlike every backend in
the DX1..DX8 family (DirectDraw/early-Direct3D has exactly one active render target).
Real readback: CreateTexture2D(D3D10_USAGE_STAGING) + CopyResource + Map/Unmap — yet
another distinct mechanism from DX1..DX7's Blt, DX8's CreateImageSurface+CopyRects, or
D3D9's GetRenderTargetData.
No logical-resolution render target / letterboxing — unlike DX8's own addition (forced by
D3D8 having no scaled-blit primitive at all), D3D10 doesn't need one: the swap chain always
matches the real window size, matching D3D11GraphicsBackend's own simpler, already-established
convention (SetVirtualResolution is inert bookkeeping, GetViewportSize returns the real
window's own pixel size via SDL).
2. Existence-gate spike (DX10-0) — see dx10-spike/README.md for the full record
#
Spike
Result
DX10-0a
D3D10CreateDeviceAndSwapChain via Wine's d3d10.dll + DXVK's d3d10core.dll/dxgi.dll
Two real environment bugs found and fixed, both fully documented in dx10-spike/README.md:
The ~/.wine-cna-d3d10 prefix (copied from the already-Vulkan-proven ~/.wine-cna-d3d11)
inherited BROKEN/dangling d3d10.dll/d3d10_1.dll symlinks (leftover from an older DXVK version
that used to ship those directly — the current DXVK 2.6.0 package only ships d3d10core.dll).
Fixed: removed those two overrides entirely, restored Wine's own real builtin files.
IDXGISwapChain::Present() crashes (a real DXVK divide-by-zero bug in readMonitorEdidFromKey,
confirmed independent of GPU and independent of whether a valid EDID is injected into the
registry) under Xvfb :99. Fixed by running against DISPLAY=:0 (the real desktop) instead —
matches an already-established precedent for this project's own D3D11/D3D12 Wine+DXVK testing.
3. Design decisions (recorded before implementation)
Wine prefix: dedicated ~/.wine-cna-d3d10 (copied from ~/.wine-cna-d3d11's already-proven
Vulkan state), d3d10core+dxgi+d3d9+d3d11 overridden to native/DXVK, d3d10/d3d10_1 left
as Wine's own real builtin (verified not dangling symlinks).
Run all tests against DISPLAY=:0, not Xvfb :99 — the only way found to avoid DXVK's own
dxgi Present()-path divide-by-zero bug (§2). scripts/run-wine-d3d10.sh defaults to this.
MVP scope, explicitly bounded (mirroring DX1's own "baseline first, lighting/richness as a
later phase" precedent, and DX8's own "fixed-function only" scope-bounding):
DrawColoredPrimitives/DrawIndexedColoredPrimitives (required by IGraphicsBackend):
real vs_4_0/ps_4_0 shader pair — world*view*projection transform + vertex-color
passthrough (matching BasicEffect(VertexColorEnabled=true), no lighting).
DrawPrimitivesEx/DrawIndexedPrimitivesEx (optional, has a safe default fallback to the
colored-primitives path): NOT overridden in this v1 — draws requesting lighting/texturing
via GpuDrawParams still render correctly as flat vertex color (XNA's own graceful
degradation), just without the lighting/texture richness D3D11GraphicsBackend's own full
10-stock-shader-variant catalog provides. Documented boundary, not a missed requirement —
IGraphicsBackend's own default exists precisely for backends at this stage.
CreateEffectBackend (custom ShaderEffect): not implemented in this v1, same boundary
DX1..DX8 all share (throws via the base default).
CreateOcclusionQuery/CreateTexture3D/CreateTextureCube/CreateRenderTargetCube/
DrawInstancedPrimitivesEx: not implemented in this v1 — all have safe base-class
defaults (return nullptr/throw), matching every backend in this family at its own MVP stage.
SpriteBatch: real GPU-quad rendering via vs_4_0/ps_4_0 (position+color+texture,
DX10-0d/0e's own proven shader), matching D3D9/D3D11's own real-shader 2D compositor
pattern (not DX1..DX8's CPU Blt-family approach, and not DX8's own from-scratch
GPU-quad-via-fixed-function approach either — this one genuinely uses shaders throughout).
State objects, not per-call render states: ID3D10BlendState/ID3D10DepthStencilState/
ID3D10RasterizerState/ID3D10SamplerState, created (and cached, keyed by the XNA-side state
description) once per distinct state combination, bound via OMSetBlendState/
OMSetDepthStencilState/RSSetState/PSSetSamplers — matches ID3D10Device's own object model
(there is no equivalent of D3D8/DX1..DX7's SetRenderState(D3DRS_*, value) at all).
SupportsCapability: ThreeD/DepthStencilBuffer/WireFrame/AnisotropicFiltering/
MultipleRenderTargets all report true (D3D10 has real OMSetRenderTargets(count>1,...)
support, a genuine, real difference from every DX1..DX8 backend). OcclusionQuery/
CustomEffects report false (out of this v1's scope, §3 above).
No logical-resolution render target (design decision in §1.5) — Present() is a direct
swapChain_->Present(syncInterval, flags) call, matching D3D11GraphicsBackend's own.
For every task: build the affected target (-DCNA_GRAPHICS_BACKEND=D3D10, MinGW cross-compile), run
the relevant CTest through scripts/run-wine-d3d10.sh (against DISPLAY=:0), and do not mark a task
✅ without both actually passing.
Phase T1 — CMake integration and skeleton
#
Task
Status
Notes
D3D10-1
Add "D3D10" to CNA_GRAPHICS_BACKEND's STRINGS; CNA_BACKEND_D3D10 option; Windows-only FATAL_ERROR gate
Full D3D10-labeled CTest suite regression + targeted cross-backend test re-run
✅
Boundaries — explicitly out of scope for this v1
Real, hardware/API-level DirectX-10-era boundaries: none — D3D10 is a full modern API. Boundaries in
THIS v1 are scope decisions (§3.3), not hardware limits: lighting/texturing via DrawPrimitivesEx
(flat vertex color only, safe default fallback), custom ShaderEffect, occlusion query, 3D/cube
textures, instancing. All have safe base-class defaults and are documented, real future-work items,
not silently dropped.
See also
dx10-spike/README.md — the full DX10-0 spike record, including the two environment-bug
investigations.
plan_dxold.md — the roadmap this backend is row 10 of.