Direct3D 9 for Wine on macOS, backed by Metal.
mtld3d replaces Wine's d3d9.dll. The PE side implements the D3D9 API and
translates it into Metal command buffers that a native library executes on the
host. The goal is the fastest Direct3D 9 implementation for Wine on macOS.
Direct3D 8 on the same core is planned. Every other Direct3D version is a
non-goal: D3D10 and later are already served on macOS by Apple's D3DMetal and
by DXMT.
Conformance serves speed rather than defining it: where matching D3D9 exactly
would cost frame time, speed wins as long as no game breaks. Those trades are
listed in docs/STATUS.md.
What the test suites run on in CI, see
ci.yml:
- macOS 15 or macOS 26, on Apple Silicon or Intel.
- A Wine from wine-build, the release CI pins, which is based on CrossOver 26. CrossOver 27 with its arm64 Wine has been tested by hand. Older Wine or CrossOver releases are not expected to work.
- A 64-bit prefix or bottle; 32-bit games run in it through WoW64.
- Rosetta 2 for an x86_64 Wine. An arm64 Wine translates x86 itself and needs none.
D3D9-era games do their floating-point math in x87 instructions, which Rosetta 2 translates slowly. Under an x86_64 Wine, run the game with x87sidecar; the wine-build releases carry the patch it needs.
Download mtld3d.tar.xz from
GitHub Releases. It installs
either as a Wine builtin, replacing the stock d3d9 for a Wine tree you own,
or as a native override per prefix, which is the route for CrossOver.
INSTALL.md, also inside the bundle, has the steps for both,
the prefix markers a hand-installed builtin needs, and a troubleshooting
section that starts with the two log lines confirming mtld3d loaded.
Runtime options live in mtld3d.conf next to the game's .exe, read at
Direct3DCreate9 and kept by the interface it returns and the devices that
interface creates. The sample documents every key with its
default and the reason it exists; a missing file means defaults. Every key
can also be set at launch through the MTLD3D_CONFIG environment variable,
which wins over the file. Keys worth knowing by name: render.scale renders
at a fraction of the presented size and lets MetalFX upscale, present.maxFps
caps the frame rate, and color.hdr.enable and cursor.software govern the
HDR present path and the cursor overlay that comes with it, both on by default
on a display with EDR headroom.
Below the file and the environment sits a third layer. A few games need
options nobody should have to discover, so mtld3d ships profiles for them,
matched on the executable name plus the version resource its vendor linked
in. A profile only supplies starting values, which the file and the
environment override key by key; RUST_LOG=mtld3d::d3d9=info names the
profile that matched. The reason behind every key is the comment on the
profile's entry in app_profile.rs.
gta-iv, Grand Theft Auto IV:adapter.spoof=amd,caps.dfFormats=false,depth.aliasSameSize=true.wow, World of Warcraft 1.12 and 3.3.5:query.flushImmediate=true.
A fullscreen device sets the display mode the game picked, as native D3D9
does, and covers the monitor with a borderless window. To keep that mode-set
virtual, set Wine's EmulateModeset key in the prefix before launching the
game:
wine reg add 'HKCU\Software\Wine\X11 Driver' /v EmulateModeset /d Y /fWith it the game window is scaled onto the physical display, letterboxed when
the aspect differs, and mouse input is mapped into the mode; without it the
whole desktop switches resolution. render.scale multiplies on top of the
mode. The Fullscreen section of INSTALL.md has the
rest.
Every process writes <exe>-<pid>.log into mtld3d-logs next to the
executable (log.dir moves it), never to the standard streams. RUST_LOG
filters it: unset, everything logs at info, and RUST_LOG=mtld3d=warn is
the single switch for the whole project. Pressing F12 in a game records the
next three frames as [dump] log lines and, with MTL_CAPTURE_ENABLED=1 in
the game's environment, as a GPU trace beside the log. The log targets, the
levels and the F12 mechanics are in
docs/ARCHITECTURE.md.
| Game | Status |
|---|---|
| World of Warcraft 1.12 | Plays, wow profile, the primary target |
| World of Warcraft 3.3.5a | Plays, wow profile |
| Half-Life 2 | Plays |
| Grand Theft Auto IV | Plays, gta-iv profile |
| 3DMark05 | Runs end to end |
| Unigine Tropics | Runs |
| Gunmetal | Starts and benchmarks |
Games that fail are tracked as game-compat issues in the
tracker; reports are welcome.
docs/STATUS.md lists what is implemented, what is not
yet, what never will be, and the divergences from D3D9 kept on purpose.
make test runs the unit tests of the pure-Rust core natively on the host and
the end-to-end suite under Wine, one leg per PE architecture. make conformance runs Wine's d3d9 test suite against the installed builtin and
gates on a per-site baseline; every remaining divergence is classified with a
written rationale in CONFORMANCE.md.
WINE_SDK must point at a Wine build or install providing wine,
winebuild and wineserver plus its development tree, exported before any
make target. The toolchain and SDK versions CI pins are in
ci.yml and the Makefile; the deployment target
is in unix/.cargo/config.toml.
make setup # one-time toolchain bootstrap; does not install Wine
make # PE side for i386 and x86_64, .so for x86_64 and arm64
make install # install into the Wine tree WINE_SDK names
make test # unit tests on the host, end-to-end suite under Wine
make check # the pre-commit gate: fmt, clippy, audit, docmake bundle packs the release tarball and its debug symbols. Every other
target and variable is documented in the Makefile beside its definition.
game.exe → d3d9.dll → mtld3d.dll → mtld3d.so
(PE, i386 or x64: one chain per arch) (Mach-O, Wine's own arch)
d3d9.dll implements the API and holds every piece of D3D9 knowledge,
mtld3d.dll is the PE shim that owns Wine's unix-call globals, and
mtld3d.so is a pure Metal abstraction layer on the host. A frame flows
through three threads: the game's API thread snapshots state, an encoder
thread translates it into Metal commands, and a submit thread replays,
presents and commits. docs/ARCHITECTURE.md has the
boundary contract, the threading model, the workspace layout and the
debugging toolkits.
CONTRIBUTING.md is the operating manual: the gates and
how to read their output, how conformance work is organised, and what a pull
request is expected to contain. The conventions live in
docs/CONVENTIONS.md. The short version: make check
and make test are green before every commit, and one pull request is one
change.
zlib.