Skip to content

Export Direct3DCreate9Ex, reporting the feature as unavailable - #384

Merged
athei merged 4 commits into
mainfrom
d3d9ex-export
Sep 6, 2026
Merged

Export Direct3DCreate9Ex, reporting the feature as unavailable#384
athei merged 4 commits into
mainfrom
d3d9ex-export

Conversation

@dacevedo12

@dacevedo12 dacevedo12 commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Symptoms

Halo 2 (Vista) does not start. Its bundled compatibility checker, PCCompat.dll, reports "Initialization failed. Either insufficient system resources were found to run the game, or game data is missing or damaged" and the game exits before creating a device.

Root cause

PCCompat.dll resolves Direct3DCreate9Ex by name to decide whether the installed runtime is the Vista-era one, and treats a null result as a runtime it will not run on. It never calls what it finds: an export that logged on entry recorded zero calls across runs that reached gameplay.

The adapter identity is not involved. Reporting a GeForce 8800 GT through adapter.spoof=nvidia fails identically, as does filling DriverVersion, DeviceIdentifier and WHQLLevel, and as does debug.capsAll=true. The title makes two D3D9 calls before giving up, GetAdapterIdentifier and GetDeviceCaps, and both succeed.

Changes

Direct3DCreate9Ex is exported and answers D3DERR_NOTAVAILABLE, clearing the out slot, which is the documented failure for a runtime without D3D9Ex; a null out slot answers D3DERR_INVALIDCALL. D3D9Ex itself stays unimplemented and QueryInterface for the Ex interfaces stays E_NOINTERFACE.

docs/STATUS.md records the export as resolvable and the feature as absent. unix/conformance/CONFORMANCE.md explains why the d3d9ex cluster now has no sites: START_TEST used to return at win_skip("Failed to get address of Direct3DCreate9Ex"), which counts as a failure under Wine; with the export resolving, every test fails to create its Ex device and skips, so the eight d3d9ex.c:5184 baseline entries go away rather than being replaced.

A new end-to-end test, windows/tests/tests/e2e/d3d9ex.rs with its COVERAGE.md row, resolves the export by GetProcAddress on the loaded module the way d3dperf.rs does, so a missing export is a test failure and not a link error, and pins the two return codes and the nulled slot.

Alternatives

Return a plain IDirect3D9 cast as IDirect3D9Ex: its first 17 vtable slots match, and it is what proved the diagnosis, but it runs off the end of the vtable as soon as a caller touches one of the five Ex-only methods.

Implement D3D9Ex: past what this title needs, and a scope decision of its own.

A per-title profile entry: profiles carry config keys, and an export is not a config key.

Leave it absent: the title cannot start for want of a symbol it never calls.

The trade this makes: no Windows d3d9.dll has ever presented "export resolves, call fails" (XP has no export, Vista onwards always succeeds), so every title that probes the name now runs the least exercised branch of its loader, the fallback after a failed Direct3DCreate9Ex. Half-Life 2's shaderapidx9.dll is such a title (mat_supports_d3d9ex, -nod3d9ex) and is the one run before merge; GTA IV never asks for the name and the WoW clients use plain Direct3DCreate9.

Verification

make check (fmt, clippy nursery + pedantic, audit, doc) green. make test ISOLATED=1: 1086 and 258 host unit tests, 475 end-to-end tests per architecture, all passing, four processes per architecture.

The new test fails before and passes after: with main's d3d9.def and lib.rs swapped in by name, make test ISOLATED=1 FILTER=d3d9ex reports e2e::d3d9ex::direct3d_create9_ex_resolves_and_reports_not_available FAILED at GetProcAddress(Direct3DCreate9Ex); with the branch's files it passes on both architectures.

Conformance: d3d9ex moves from baseline(failed=1) current(failed=0) STALE BASELINE to ok, device, visual and stateblock unchanged; CI's conformance legs on every image, the Intel @mac2 entries included, confirm the baseline edit.

In Halo 2 the device is created and gameplay renders; at RUST_LOG=mtld3d::d3d9=warn the new warning is never emitted, confirming the export is resolved and not called. Half-Life 2 run from this build: pending.

@dacevedo12

Copy link
Copy Markdown
Collaborator Author

Looks like exporting the symbol un-gates the entire d3d9ex.c suite, ouch.

@dacevedo12
dacevedo12 force-pushed the d3d9ex-export branch 2 times, most recently from e418d29 to b28424a Compare September 6, 2026 02:15
athei
athei previously requested changes Sep 6, 2026

@athei athei left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the diagnosis is convincing and the code itself passes the conventions: no new static, key, wire field, dependency, derive or allow, null_out and the HRESULT constants reused, log_once_warn! on the stub, doc shape right, .def updated, and the baseline hand-edit for all eight variants is confirmed by the Intel conformance legs. Five things before it can land.

  1. docs/STATUS.md line 48 still says "D3D9Ex: no Direct3DCreate9Ex, shared handles or D3D9On12". The description says both STATUS.md and CONFORMANCE.md record the export as absent and that this narrows the decision, but only CONFORMANCE.md is in the diff, so the status document now contradicts the DLL. That is the missing companion edit CONTRIBUTING lists under what sends a PR back.

  2. Nothing fails before this change and passes after it. CONTRIBUTING asks for that on any observable-behaviour claim, and the precedent for the last export addition is windows/tests/tests/e2e/d3dperf.rs with its COVERAGE.md row: resolve by GetProcAddress on the loaded module, no harness, so a missing export is a test failure rather than a link error. The same shape pins this one: the name resolves, the call answers D3DERR_NOTAVAILABLE with the out slot nulled, and a null out slot answers D3DERR_INVALIDCALL. The audit only checks the COVERAGE row once a file exists, which is why nothing caught it.

  3. The alternatives leave out the risk this trades for. No Windows d3d9.dll has ever presented "export resolves, call fails": XP has no export and Vista onwards always succeeds, so the failure branch after a successful lookup is the least exercised path in every engine, and every title that probes the name changes code path with this PR. Half-Life 2's shaderapidx9.dll resolves Direct3DCreate9Ex by name (mat_supports_d3d9ex, mat_disable_d3d9ex, -nod3d9ex), so it moves from the plain create to the Ex attempt plus fallback. It needs a run from this branch before merge. GTA IV never asks for the name and the WoW clients use plain Direct3DCreate9, so those are unaffected.

  4. The verification section is stale. The unload::exception_after_free_library_survives failure it excuses is #378, fixed by #395, which is already in your rebased base. The nine visual sites paragraph describes the local machine, not the change. The verification should name the gates that ran (make check is fmt, clippy, audit and doc; make test on both arches; conformance on every CI image) and say which way the new test went.

  5. Nits. The doc comment says ppD3D where the parameter is out. In CONFORMANCE.md the new ### d3d9ex.c heading has no .c/ so the parser reads it as prose, which works, but the file's shape for a cluster with nothing to classify is the bare ### d3d9ex.c clusters header, and the "previously mis-attributed to test_scene" sentence is history rather than rationale.

@athei

athei commented Sep 6, 2026

Copy link
Copy Markdown
Owner

I am taking this over from here: the five points above land as commits on top of yours on d3d9ex-export, and the merge waits for a Half-Life 2 run from that build.

dacevedo12 and others added 3 commits September 6, 2026 12:12
## Symptoms

Halo 2 (Vista) does not start. Its bundled compatibility checker,
`PCCompat.dll`, reports "Initialization failed. Either insufficient system
resources were found to run the game, or game data is missing or damaged"
and the game exits before creating a device.

## Root cause

`PCCompat.dll` resolves `Direct3DCreate9Ex` by name to decide whether the
installed runtime is the Vista-era one, and treats a null result as a runtime
it will not run on. It never calls what it finds: an export that logged on
entry recorded zero calls across runs that reached gameplay.

The adapter identity is not involved. Reporting a GeForce 8800 GT through
`adapter.spoof=nvidia` fails identically, as does filling `DriverVersion`,
`DeviceIdentifier` and `WHQLLevel`, and as does `debug.capsAll=true`. The
title makes two D3D9 calls before giving up, `GetAdapterIdentifier` and
`GetDeviceCaps`, and both succeed.

## Changes

`Direct3DCreate9Ex` is exported and answers `D3DERR_NOTAVAILABLE`, clearing
the out pointer, which is the documented failure for a runtime without
D3D9Ex. D3D9Ex itself stays unimplemented.

`docs/STATUS.md` and `unix/conformance/CONFORMANCE.md` both record the export
as deliberately absent, so this narrows that decision: the feature remains out
of scope, only the entry point becomes resolvable.

The export un-gates `d3d9ex.c`, whose `START_TEST` used to return at
`win_skip("Failed to get address of Direct3DCreate9Ex")`. The suite now runs
and reports no failures: every test fails to create its Ex device and skips,
and a skip is not a failure. So the baselined site goes away rather than
being replaced, and the eight `d3d9ex.c:5184 count=1` entries are removed with
the cluster prose rewritten to say why the suite is empty.

## Alternatives

Return a plain `IDirect3D9` cast as `IDirect3D9Ex`: its first 17 vtable slots
match, and it is what proved the diagnosis, but it runs off the end of the
vtable as soon as a caller touches one of the five Ex-only methods.

Implement D3D9Ex: past what this title needs, and a scope decision of its own.

A per-title profile entry: profiles carry config keys, and an export is not a
config key.

Leave it absent: the title cannot start for want of a symbol it never calls.

## Verification

`make check` and `make test`: 1033 and 200 host unit tests, 440 end-to-end
tests on both architectures. `unload::exception_after_free_library_survives`
fails identically on an unmodified tree at this commit and is unrelated.

`make conformance-i686`: `d3d9ex` moves from `baseline(failed=1)
current(failed=0) STALE BASELINE` to `ok`, and `device` and `stateblock` are
unchanged. `visual` reports nine new sites on this machine, but a control run
of the same target on an unmodified tree at this commit reports the identical
nine, so they are not from this change.

In the title, the device is created and gameplay renders. At
`RUST_LOG=mtld3d::d3d9=warn` the new warning is never emitted, confirming the
export is resolved and not called.
An end-to-end test resolves the export by name and checks the documented
failure of a runtime without D3D9Ex: D3DERR_NOTAVAILABLE with the out slot
nulled, D3DERR_INVALIDCALL for a null slot. STATUS.md no longer claims the
export is absent, and the d3d9ex conformance cluster prose sits under its
clusters header without the attribution history.
@athei
athei dismissed their stale review September 6, 2026 08:21

Findings addressed on the branch directly; review state no longer applies.

@athei

athei commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Half Life 2 still working.

@athei
athei merged commit 4caa520 into main Sep 6, 2026
17 checks passed
@athei
athei deleted the d3d9ex-export branch September 6, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants