Nugget
Loading...
Searching...
No Matches
raster-expected-phase8.h
Go to the documentation of this file.
1/*
2
3MIT License
4
5Copyright (c) 2026 PCSX-Redux authors
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26
27#pragma once
28
29// Phase-8 expected hardware-truth values for 4-vert textured quad
30// rasterization. All values HW_VERIFIED on SCPH-5501.
31//
32// Findings worth flagging:
33//
34// 1. Top-left rule applies uniformly: right-vertex column and
35// bottom-row pixels of axis-aligned quads are NOT drawn -
36// sentinel reads back. (4,0) of a 5x4 quad reads 0xDEAD; (0,0)
37// of a parallelogram whose apex is a single point reads 0xDEAD.
38// The right vertex of every quad row is excluded.
39//
40// 2. GP0(0x2E) semi-trans does NOT blend when the texture's mask
41// bit is clear. PS1 hardware gates semi-trans blending on bit 15
42// of the sampled texel (texture-mask-bit). All our fixture
43// textures encode bit 15 = 0, so QFS4 / QFS8 read back the SAME
44// values as their opaque QFA counterparts. The semi-trans CODE
45// PATH is exercised, but the blend doesn't trigger - the soft
46// renderer's drawPoly4TEx*_S bodies must replicate this gating.
47//
48// 3. 3-vert / 4-vert path convergence at degenerate quads: a 4-vert
49// quad with v3 == v2 produces interior pixels identical to a
50// 3-vert reference draw at every probed position. The 4-vert
51// math collapses correctly when the fourth vertex is redundant.
52//
53// 4. QFD parallelogram half-pixel-bias bug (4-vert flat-textured):
54// drawPoly4TEx4/TEx8/TD all init the inner-loop posX from m_leftU
55// directly, which is the U value at the FRACTIONAL left edge
56// m_leftX, not at the integer first-drawn-pixel center. When the
57// top-left ceil rule (xmin = (m_leftX + 0xFFFF) >> 16) shifts the
58// first pixel a fractional gap to the right of m_leftX, hardware
59// samples U at the pixel center (xmin + 0.5) - which adds
60// (xmin*65536 + 0x8000 - m_leftX) * (m_rightU - m_leftU) /
61// (m_rightX - m_leftX) to posX. Redux skips that advance, so U
62// is sampled at the m_leftX fractional position instead of the
63// pixel center.
64//
65// For QFD geometry (left edge slope 4/7 per row), frac(m_leftX)
66// cycles 0/0.571/0.143/0.714/0.286/0.857/0.429 across rows 0..6.
67// Drift fires when frac in (0, 0.5] (so the ceil-residue plus
68// the half-pixel bias crosses 1.0 LSB in U): rows 2, 4, 6 fail
69// by -1 LSB on U. Rows 0, 1, 3, 5 pass. Verified at x=4 and x=10
70// for both QFD4 (4-bit indexed) and QFD15 (15-bit direct) - the
71// drift is in U only, V is unaffected since the left edge has
72// zero U/V slope in this geometry.
73//
74// Same arithmetic family as the gouraud color precision finding
75// (phases 7, 10), but operating on UV interpolation in the
76// 4-vertex flat-textured path rather than gouraud color in the
77// 3-vertex path. Affects all six drawPoly4TEx* bodies (opaque +
78// semi-trans variants of TEx4, TEx8, TD). The pair-sampler
79// terminal-odd-pixel question (audit finding #8) is structurally
80// inert here - the bug is at the row-start posX init, not at the
81// terminal sampler.
82
83#include "raster-helpers.h"
84#include "texture-fixtures.h"
85
86// --------------------------------------------------------------------------
87// QFA4: axis-aligned 16x8 4-bit quad
88// --------------------------------------------------------------------------
89
90#define QFA4_0_0 0x03e0u /* CLUT4[0] = vram555(0, 31, 0) */
91#define QFA4_14_0 0x022eu /* CLUT4[14] = vram555(14, 17, 0) */
92#define QFA4_0_6 0x03e0u
93#define QFA4_14_6 0x022eu
94#define QFA4_7_3 0x0307u /* CLUT4[7] = vram555(7, 24, 0) */
95#define QFA4_3_5 0x0383u /* CLUT4[3] = vram555(3, 28, 0) */
96
97// --------------------------------------------------------------------------
98// QFA8: axis-aligned 32x8 8-bit quad. CLUT8[u] = vram555(u&31, (255-u)&31,
99// (u>>5)&31). The formula's (255-u)&31 reduces mod 32 so e.g. u=30 gives
100// (255-30)=225, 225&31=1 -> green channel = 1 not 31.
101// --------------------------------------------------------------------------
102
103#define QFA8_0_0 0x03e0u /* CLUT8[0] = vram555(0, 31, 0) */
104#define QFA8_30_0 0x003eu /* CLUT8[30] = vram555(30, 1, 0) */
105#define QFA8_0_6 0x03e0u
106#define QFA8_30_6 0x003eu
107#define QFA8_15_3 0x020fu /* CLUT8[15] = vram555(15, 16, 0) */
108#define QFA8_22_5 0x0136u /* CLUT8[22] = vram555(22, 9, 0) */
109
110// --------------------------------------------------------------------------
111// QFA15: axis-aligned 16x8 15-bit. texel(u, v) = vram555(u&31, v&31,
112// (u+v)&31). (0,0) excluded by top-left rule - reads sentinel.
113// --------------------------------------------------------------------------
114
115#define QFA15_0_0 RASTER_SENTINEL /* top-left vertex excluded */
116#define QFA15_14_0 0x380eu /* vram555(14, 0, 14) */
117#define QFA15_0_6 0x18c0u /* vram555(0, 6, 6) */
118#define QFA15_14_6 0x50ceu /* vram555(14, 6, 20) - blue=20 overflows 5-bit at bit 14 */
119#define QFA15_7_3 0x2867u /* vram555(7, 3, 10) */
120#define QFA15_3_5 0x20a3u /* vram555(3, 5, 8) */
121
122// --------------------------------------------------------------------------
123// QFD4: parallelogram-skewed 4-bit quad. UV interpolation along the
124// slanted edges produces non-trivial per-pixel UV positions.
125// --------------------------------------------------------------------------
126
127#define QFD4_0_0 0x03e0u
128#define QFD4_8_0 0x02e8u
129#define QFD4_4_3 0x03a2u
130#define QFD4_10_3 0x02e8u
131#define QFD4_4_6 0x03c1u
132#define QFD4_14_6 0x028bu
133
134/* Per-row drift probes (added to characterize the QFD half-pixel-bias
135 bug). Predicted via U = floor(x + 0.5 - leftX[y]) with leftX[y]
136 advancing by 4/7 per row. Rows where frac(leftX) lands in (0, 0.5]
137 should expose the bug (Redux samples one LSB low on U). Locked
138 HW_VERIFIED once captured on SCPH-5501. */
139#define QFD4_4_1 0x0383u /* leftX=0.571; HW U=3 (pass row) */
140#define QFD4_10_1 0x02c9u /* HW U=9 (pass row) */
141#define QFD4_4_2 0x0383u /* leftX=1.143; HW U=3, Redux U=2 (drift) */
142#define QFD4_10_2 0x02c9u /* HW U=9, Redux U=8 (drift) */
143#define QFD4_4_4 0x03a2u /* leftX=2.286; HW U=2, Redux U=1 (drift) */
144#define QFD4_10_4 0x02e8u /* HW U=8, Redux U=7 (drift) */
145#define QFD4_4_5 0x03c1u /* leftX=2.857; HW U=1 (pass row) */
146#define QFD4_10_5 0x0307u /* HW U=7 (pass row) */
147
148#define QFD15_0_0 RASTER_SENTINEL /* apex excluded by top-left */
149#define QFD15_8_0 0x2008u
150#define QFD15_4_3 0x1462u
151#define QFD15_10_3 0x2c68u
152#define QFD15_4_6 0x1cc1u
153#define QFD15_14_6 0x44cbu
154
155/* Direct-15 mirror probes. Texel(u,v) = vram555(u, v, (u+v)&31). */
156#define QFD15_4_1 0x1023u /* HW u=3 v=1 (pass row) */
157#define QFD15_10_1 0x2829u /* HW u=9 v=1 (pass row) */
158#define QFD15_4_2 0x1443u /* HW u=3 v=2 (drift) */
159#define QFD15_10_2 0x2c49u /* HW u=9 v=2 (drift) */
160#define QFD15_4_4 0x1882u /* HW u=2 v=4 (drift) */
161#define QFD15_10_4 0x3088u /* HW u=8 v=4 (drift) */
162#define QFD15_4_5 0x18a1u /* HW u=1 v=5 (pass row) */
163#define QFD15_10_5 0x30a7u /* HW u=7 v=5 (pass row) */
164
165// --------------------------------------------------------------------------
166// QFO[4|8|15]: 5x4 axis-aligned quad. The right-vertex column (x=4) is
167// EXCLUDED by hardware's top-left rule across all depths - reads
168// sentinel at every row. The "terminal sampler" question the audit
169// raised doesn't surface as a hardware-visible pixel at the
170// right-vertex column; it only matters for soft renderer pixels
171// hardware excludes anyway. Note: with hardware's `(rightX-1)>>16`
172// rule the LAST drawn column is x=3, and the texture coords at that
173// pixel match a standard per-pixel UV sample - no terminal-pair
174// asymmetry to characterize against hardware.
175// --------------------------------------------------------------------------
176
177#define QFO4_TERMINAL_4_0 RASTER_SENTINEL
178#define QFO4_TERMINAL_4_1 RASTER_SENTINEL
179#define QFO4_TERMINAL_4_2 RASTER_SENTINEL
180#define QFO4_INTERIOR_2_1 0x03a2u /* CLUT4[2] = vram555(2, 29, 0) */
181#define QFO4_INTERIOR_3_2 0x0383u /* CLUT4[3] = vram555(3, 28, 0) */
182
183#define QFO8_TERMINAL_4_0 RASTER_SENTINEL
184#define QFO8_TERMINAL_4_1 RASTER_SENTINEL
185#define QFO8_TERMINAL_4_2 RASTER_SENTINEL
186#define QFO8_INTERIOR_2_1 0x03a2u /* CLUT8[2] = vram555(2, 29, 0) */
187
188#define QFO15_TERMINAL_4_0 RASTER_SENTINEL
189#define QFO15_TERMINAL_4_1 RASTER_SENTINEL
190#define QFO15_TERMINAL_4_2 RASTER_SENTINEL
191#define QFO15_INTERIOR_2_1 0x0c22u /* vram555(2, 1, 3) */
192
193// --------------------------------------------------------------------------
194// QFS4 / QFS8: semi-trans. PS1 hardware gates the blend on the
195// texture's bit-15 mask. Our fixture textures encode bit 15 = 0 in
196// every texel/CLUT entry, so the semi-trans command produces the same
197// values as the opaque command - no blend applied.
198// --------------------------------------------------------------------------
199
200#define QFS4_0_0 0x03e0u /* same as QFA4_0_0 - no blend */
201#define QFS4_7_3 0x0307u /* same as QFA4_7_3 */
202#define QFS4_14_6 0x022eu /* same as QFA4_14_6 */
203
204#define QFS8_0_0 0x03e0u /* same as QFA8_0_0 */
205#define QFS8_15_3 0x020fu /* same as QFA8_15_3 */
206#define QFS8_30_6 0x003eu /* same as QFA8_30_6 */
207
208// --------------------------------------------------------------------------
209// QFDEG / QFDEG_REF: degenerate 4-vert quad collapses to 3-vert
210// triangle. Both produce identical pixels at every probed position.
211// --------------------------------------------------------------------------
212
213#define QFDEG_0_0 0x03e0u
214#define QFDEG_7_3 0x0307u
215#define QFDEG_3_5 0x0383u
216
217#define QFDEG_REF_0_0 QFDEG_0_0 /* 3-vert ref matches 4-vert degenerate */
218#define QFDEG_REF_7_3 QFDEG_7_3
219#define QFDEG_REF_3_5 QFDEG_3_5