Repository navigation
Constantine: renders and plays its first level (resolve program, host visibility tests, seeds) - #109
Merged
Merged
Conversation
…self With Begin and End replaced and SetVertexData* not (the 5960 online dashboard: its symbols are not identified), the vertices between them are in the push buffer, written by the title's own code, and End's push_skip walked past them without drawing: every such quad had no vertices. End now walks them in while the group is open. A title whose SetVertexData* are replaced writes nothing there, so nothing changes for it.
D3DDevice_End drew its vertices under the shader the API last selected. A program started through the push buffer since then is what the NV2A runs, and the walk's own draws already use it (pushed_program_begin); End now does the same. Constantine resolves its 1280x480 supersampled screen inside Swap with one triangle drawn this way: the XDK starts a two-instruction program (oPos = v0, oT0 = v9) and calls Begin / End. Under the title's 2D program, which reads its texture coordinate from v2, every pixel sampled one texel and each frame after the intro movies was a single flat colour. The main menu now draws.
A title that plays its movies through D3DDevice_UpdateOverlay presents without Swap, so RECOMP_HLE_D3D8_DUMP and F11 never saw those frames. The overlay's own present now dumps first. Found on Constantine, whose Bink intro movies go this way.
GetVisibilityTestResult answered every test "0 pixels" once a title spun on it: nothing executes the push buffer, so no report is ever written. Constantine culls its scenery and its hero by these tests, 53 a frame in its first level, and Constantine himself and most of his apartment were never drawn. The push-buffer walk now hands SET_ZPASS_PIXEL_COUNT_ENABLE (0x17CC) and GET_REPORT (0x17D0) to the HLE. Between the two the RHI counts the samples of each host draw (rhi_occlusion_*: one Vulkan query a draw, so a test can span a render-target change; one D3D11 query a test), and GetVisibilityTestResult answers with the count of the newest closed test whose report offset names its index (bits 4-11 of the offset, the XDK's 16-byte slots in a 4 KB page). Counts are scaled to guest samples. All closed tests are read back together, one GPU wait a batch: one at a time it was ~80 waits a frame and cost a fifth of the frame rate. Capped, Constantine's level holds 60 fps; uncapped ~430 swaps / 5 s against ~505 with RECOMP_HLE_D3D8_VISIBILITY=0, which also draws less. The D3D11 half is not yet built or run on Windows.
From a run's log (tools.seed_from_log). 0x001DF280 is a callback the title stores at 0x35A3F4; the disassembler ran a six-entry jump table at 0x1DF268 into it, so it was lifted as the middle of sub_001DF1B0 and called over 1000 times unresolved.
Constantine's Bink player asks "is the next frame due?" -- a mutex taken and released -- and reads the TSC round and round between movie frames: about two million kernel calls a second, 83% of a core, during every movie. The console spins the same way, but on a handheld it is heat and battery for nothing. A thread that reads the TSC 256 times within a millisecond of host time, four windows running, now sleeps 1 ms (xbox_GuestSleep, so the guest lock is dropped) and reads the clock afresh. A loop waiting for a moment overshoots it by about that much at most; work that reads the clock as it goes reads it far less often. RECOMP_TSC_POLL_SLEEP=0 lets it spin. Constantine's intro movies, 50 s: main-thread CPU 44.2 s -> 6.3 s, mutex waits 77.7M -> 5.4M; the same movie frames at the same times and the movie's sound consumed at the same rate. In-level, uncapped, unchanged (~438 swaps / 5 s).
The pass that finds the function after a switch's tables walks over the byte index table, taking every byte below the dword table's entry count as an index. A switch with more cases than the next function's first bytes are worth cannot tell the two apart by value: Constantine's at 0x0004E220 has ~156 entries, so "56 8B F1" (push esi; mov esi, ecx) at 0x0004E490 read as two more indices, a function was started at the F1 in the middle of the mov, and the lifted method lost "mov esi, ecx". Its vtable calls went through whatever esi held -- the ICALLs to 0x804AF540 and 0 -- and the Select Difficulty menu was drawn with no options. The walk now stops at an address that is named as code -- a function or candidate start, a direct branch from inside a function, or a data word beside another code pointer (a vtable; XBEs flag .rdata executable, so code sections are told by name) -- and could start a function (first byte 0x0F or above, a body that decodes). The usual skip at the walk's end still looks only at existing starts, so a named function after the tables is still recovered here. Branches decoded from the index bytes themselves, lone data words and alias entries are not evidence: each lost a real function in a first version of this. Constantine against the previous disassembler: one entry fewer (0x0004E492, the split) and nothing else changed; its difficulty menu draws Easy, Medium and Hard.
bechapm
added a commit
to bechapm/DirectXbox-Recomp-D3D9
that referenced
this pull request
Nov 1, 2026
Catch up with phobos665 main (phobos665#107, phobos665#109), and D3D9 occlusion queries
bechapm
pushed a commit
to bechapm/DirectXbox-Recomp-D3D9
that referenced
this pull request
Nov 1, 2026
Brings this fork's main level with upstream: phobos665#109 (Constantine: resolve program, host-counted visibility tests) and the touch-control default. No conflicts. feat/d3d9-remix already has both (PR #7). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KicvamdgSFkngztRkMUVsV
bechapm
added a commit
to bechapm/DirectXbox-Recomp-D3D9
that referenced
this pull request
Nov 2, 2026
Bring main level with phobos665 main (phobos665#109, touch default)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Toolkit fixes from bringing up Constantine (5343000F, XDK 5849). With these it boots, plays its Bink intro movies, reaches the main menu and plays its first level at a native 60 fps, with Constantine and his apartment drawn. The title project itself stays local (
titles/is ignored).Changes
Endruns the program the XDK started (c3bd8eba). Swap scales the 1280x480 supersampled screen down with one triangle: the XDK starts a two-instruction program (oPos = v0,oT0 = v9) through the push buffer and callsBegin/End.Enddrew it under the title's 2D program, which reads its texcoord fromv2, so every pixel sampled one texel and each frame after the movies was a single flat colour.Endnow usespushed_program_beginas the walk's own draws do.dd28b57d). They were answered "0 pixels". Constantine culls its scenery and its hero by them (53 a frame), so neither was drawn.SET_ZPASS_PIXEL_COUNT_ENABLE(0x17CC) andGET_REPORT(0x17D0) to the HLE.rhi_occlusion_*, one Vulkan query per draw, one D3D11 query per test.GetVisibilityTestResultanswers with the matching test's count, scaled to guest samples.RECOMP_HLE_D3D8_VISIBILITY=0switches it off.39dbec03). They now catch movies presented byUpdateOverlaywithout a Swap.RECOMP_TSC_POLL_SLEEP=0turns it off. Over 50 s of intro movies, main-thread CPU went from 44.2 s to 6.3 s, with the same movie frames at the same times. In-level uncapped is unchanged.f61788a5,tools/disasm). The disassembler's walk over a switch's index bytes treated56 8B(push esi; mov esi, ecx) as two more indices, because the switch has about 156 cases. It started a function in the middle of themov, so the lifted method lostesi = ecx, its vtable calls went wild, and Select Difficulty showed no options. The walk now stops at a plausible function start that's named as code. Compared with the old disassembler, Constantine differs by exactly that one bogus entry. Other titles change only when re-lifted.259d78e7): six thread starts, plus a callback (0x001DF280) that the disassembler had run into from a jump table before it. It was called over 1000 times unresolved.1e4536a8is the same immediate-mode vertex fix as in Xbox Live without a console backup: generated console, Insignia registration, updater from the player's dashboard #108, cherry-picked here.Measured (macOS arm64, Vulkan)
RECOMP_HLE_D3D8_VISIBILITY=0. That run also draws less, because the culled objects aren't drawn.Not done / needs checking
rhi_occlusion_*has not been built or run. It needs a Windows build, plus the TS2 and BLiNX regression before merging, because theEndchange, the visibility tests and the clock back-off affect every title.