Skip to content

fix(vif1): parse VIFcodes between a DIRECT image tag and its pixel data - #269

Open
drakolordx7 wants to merge 1 commit into
ran-j:mainfrom
drakolordx7:fix/vif1-direct-image-continuation
Open

drakolordx7 wants to merge 1 commit into
ran-j:mainfrom
drakolordx7:fix/vif1-direct-image-continuation

Conversation

@drakolordx7

@drakolordx7 drakolordx7 commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

A VIF1 DIRECT can end with a PATH2 IMAGE GIFtag whose pixels arrive in a later DIRECT, typically MARK; DIRECT n in the next DMAtag's TTE words. processVIF1Data took the bytes right after the first DIRECT as pixel data, but those are the next VIFcodes. Every such upload (fonts, CLUTs, UI atlases) was shifted by 8 bytes and its last 8 bytes were parsed as VIFcodes: garbled text and dithered sprite edges (seen in Killzone, SCUS-97402).

VIFcodes are now always parsed, and only the payload of a DIRECT/DIRECTHL is re-wrapped in a synthesized IMAGE tag while an image is pending. Raw continuation is kept only for a DIRECT cut off at the end of a buffer (m_vif1PendingDirectQwc). Checked byte for byte against real PCSX2's EE RAM (through a PINE savestate) in Killzone.

Please look at two existing tests: they placed pixel qwords directly after a complete DIRECT with no VIFcode, which hardware decodes as a VIFcode. The first is now the buffer-cut-off case; the second ("finds an image continuation after packed setup") gets a DIRECT 1 in front of its pixels. A new DIRECT; MARK; DIRECT test fails without the fix. ps2x_tests 437/437.

Made by drakolord and assisted with Claude Code.

A VIF1 DIRECT can end with a PATH2 GIF IMAGE tag whose pixel data comes
in a later DIRECT, typically "MARK; DIRECT n" in the TTE words of the next
DMAtag. processVIF1Data() remembered the pending image (qwc) and then
treated the bytes right after the first DIRECT as that pixel data. Those
bytes are the next VIFcodes, so every such upload (fonts, CLUT and UI
atlas uploads) was shifted by the two VIFcode words: 2 CT32 pixels or 16
T4 pixels. The last 8 bytes of the data were then parsed as VIFcodes,
which showed up as garbled text and dithered sprite edges.

VIFcodes are now always parsed. When an image is pending, only the payload
of a DIRECT/DIRECTHL is re-wrapped in a synthesized IMAGE tag and
forwarded to PATH2 (forwardVif1DirectData). Raw continuation without
VIFcodes is kept only where hardware has it: a DIRECT cut off at the end
of a processVIF1Data() buffer (m_vif1PendingDirectQwc / m_vif1PendingDirectHl,
reset together with the pending image state).

Two existing tests ("VIF1 DIRECT image tag can continue with raw image
qwords" and "VIF1 DIRECT finds an image continuation after packed setup")
placed the pixel qwords directly after a complete DIRECT with no VIFcode in
front of them, which hardware would decode as a VIFcode. They now put the
pixels behind a DIRECT (and the first one checks the buffer cut-off case,
which is the one that really continues without VIFcodes). A new test covers
"DIRECT; MARK; DIRECT" and fails without the fix.

Found while bringing up a game's UI and font rendering, and verified byte
for byte against the EE RAM of a real PCSX2 through a savestate.

Made by drakolord and assisted with Claude Code.
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.

1 participant