Files
parallel-ps2/tests/gs
LibretroAdmin 1f992391b2 GSdx: re-apply the register handlers' normalisations when a state is loaded
Every GS register a game writes goes through a GIFRegHandler, and a good
many of those do more than store the value. They mask a field to its real
width, clamp it to a range the rest of the emulator relies on, substitute
a value a particular game depends on, or maintain an invariant between two
registers. Defrost restores registers with a raw copy, so none of it
happens, and each of those guarantees is void for the rest of the session.

Two registers were being re-normalised by hand there. That is how the
ALPHA clamp came to be noticed -- it had an 81-entry table downstream of
it, so it showed. The other nine did not show, which is the only reason
they were still there. Doing this one register at a time is what produced
the gap, so this does it in one place instead, and a new handler that
transforms a value needs a line in it.

What was missing:

  Q, twice over. GIFRegHandlerRGBAQ replaces a NaN, and the comment names
  Silent Hill and says it breaks the min/max in GSVertexTrace.
  GIFPackedRegHandlerSTQ replaces a zero, and the comment names Vexx and
  says it is there to avoid a division by zero. A state file put both
  straight back, past everything downstream written to assume they were
  gone -- including the conversions hardened earlier in this series.

  FRAME.FBW, clamped to 32 by the handler. The field is 6 bits, so a state
  can ask for 63, and Defrost fed the raw value into the offset
  recomputation and the texture cache sizes a target from FBW * 64.

  The FRAME/ZBUF coupling. On hardware a Z-format FRAME forces the Z
  buffer to colour swizzling, Powerdrome clears Z by relying on it, and
  both handlers re-establish it on every write to either register, so the
  pair is never seen uncoupled -- except after a restore, which brings
  them back independently and then derives offset.zb and offset.fzb4 from
  the mismatch.

  TEX0.CPSM, restricted to 1010b. Defrost calls ApplyTEX0 for the active
  context only, and after m_prev_env is taken, so neither copy of the
  other context was ever masked.

  UV, masked to 14 bits. GSRegs.h states the handlers are what keeps the
  pad bits clear; a raw restore made the header wrong.

  The FRAME PSM substitution for the three formats that address part of a
  32-bit pixel, and the SCANMSK latch that Reset had just cleared.

The call goes before m_prev_env is taken, which also fixes the ordering
half of the CPSM case, and before the scissor and offset derivation, which
was being computed from unnormalised FRAME and ZBUF.

tests/gs/gs_restore_norm.cpp links the real GSVector.cpp and runs the
handlers' own vector expressions against the scalar ones the restore path
uses, bit pattern by bit pattern. The transcription was the risk here, not
the idea: both handlers test Q's bit pattern rather than its value, so a
negative zero is deliberately not substituted, and a scalar version
written the tidier way silently disagrees on exactly that input. The test
fails on it.

Two things looked worse than they are and are recorded rather than
changed. A restored m_tr.x/m_tr.y reaches a write-image routine as raw
ints, but GSOffset::bn wraps the block number modulo GS_MAX_BLOCKS before
BlockPtr indexes m_vm8, so a wild coordinate lands somewhere wrong inside
the 4MB VM rather than outside it. And ZBUF.PSM reaches a shift count as
m_psm[PSM].fmt * 8, which would be the dangerous shape, but fmt is 0..3
for all 64 table entries, so the shift is in range whatever the PSM.
2026-09-22 16:32:26 +02:00
..
2026-08-29 15:26:07 +02:00