Nugget
Loading...
Searching...
No Matches
raster-expected-phase7.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-7 expected hardware-truth values for gouraud color precision.
30//
31// All values HW_VERIFIED on real SCPH-5501 by running the binary via
32// Unirom + psxup.py and grepping ^OBS lines from the captured serial
33// log. Mismatches that surface when the soft renderer runs against
34// this oracle are the deliverable - they enumerate the punch list for
35// the delta-precision refactor.
36//
37// Naming: GC = Gouraud Canonical (size 1/2/3 = 8/32/128).
38// GV = Gouraud Vertical R-only sweep.
39// GH = Gouraud Horizontal R-only sweep.
40// GS = Gouraud Saturation / vertex-exactness probes.
41// GD = Gouraud Dither.
42// GO = Gouraud Orientation (vertex-order permutations).
43//
44// Color encoding: VRAM 5:5:5 with R at bits 4:0, G at bits 9:5, B at
45// bits 14:10, mask at bit 15. The GP0(0x30) command word packs vertex
46// colors as 8:8:8 with R at bits 7:0, G at bits 15:8, B at bits 23:16
47// (rasterCmdColor() shifts 5-bit channel values up by 3 to land in
48// command form). Values below are VRAM-side as read back via GP0(0xC0).
49//
50// Notable HW findings (verified 2026-05-15):
51//
52// 1. Vertex-exactness: apex pixels of every gouraud triangle land at
53// exactly the apex vertex color. No accumulator-init drift at the
54// origin row.
55//
56// 2. Vertex-order independence: All six permutations of GC1's three
57// vertices produce identical interior pixel values. The PS1
58// gouraud rasterizer is handedness-free.
59//
60// 3. Symmetric vertical / horizontal interpolation: identical R-only
61// gradient triangles probed along the left edge (GV) and the top
62// edge (GH) produce identical per-row / per-pixel values for the
63// same N. The two truncating delta paths share their arithmetic.
64//
65// 4. Large-triangle apex precision: GC3 (128x128) reads (1,1) at
66// R=30 instead of R=31. Tiny offset from apex already shows
67// cross-axis interpolation drift at the 5-bit boundary.
68//
69// 5. Sub-LSB triangles: a triangle with apex R=1 and base R=0 holds
70// R=1 only at the apex row. By y=2 the per-row 8-bit delta has
71// dropped the accumulator below the 5-bit truncation threshold
72// and all subsequent rows read R=0.
73//
74// 6. Dither overlay: same canonical 32x32 RGB triangle reads back
75// different per-pixel values under E1[9] = 1, with adjacent
76// pixels' 5-bit channels differing by single LSBs in a stable
77// 4-pixel-periodic pattern.
78
79#include "raster-helpers.h"
80
81// --------------------------------------------------------------------------
82// GC: Canonical RGB-vertex triangle at three sizes
83// --------------------------------------------------------------------------
84//
85// Layout (size N):
86// v0 = (0, 0) RASTER_CMD_RED -> 5:5:5 R=31 G=0 B=0
87// v1 = (N, 0) RASTER_CMD_GREEN -> 5:5:5 R=0 G=31 B=0
88// v2 = (0, N) RASTER_CMD_BLUE -> 5:5:5 R=0 G=0 B=31
89
90// -- GC1: 8x8 -- HW_VERIFIED
91#define GC1_V0_R 0x001fu /* apex (0,0) = pure R */
92#define GC1_TOP_X4 0x022du /* (4,0) R=13 G=17 B=0 - 4/7 along R->G */
93#define GC1_LEFT_Y4 0x440du /* (0,4) R=13 G=0 B=17 - 4/7 along R->B */
94#define GC1_INTERIOR_1_1 0x1096u /* (1,1) interior near apex */
95#define GC1_INTERIOR_2_2 0x210du /* (2,2) interior */
96#define GC1_INTERIOR_3_3 0x35a4u /* (3,3) interior */
97#define GC1_INTERIOR_1_3 0x348du /* (1,3) more B than G */
98#define GC1_INTERIOR_3_1 0x11adu /* (3,1) more G than B */
99
100// -- GC2: 32x32 -- HW_VERIFIED
101#define GC2_V0_R 0x001fu /* apex pure R */
102#define GC2_TOP_X16 0x020fu /* (16,0) midpoint of R->G top edge */
103#define GC2_LEFT_Y16 0x400fu /* (0,16) midpoint of R->B left edge */
104#define GC2_INTERIOR_8_8 0x210fu /* (8,8) interior */
105#define GC2_INTERIOR_16_8 0x2207u /* (16,8) right-of-centroid */
106#define GC2_INTERIOR_8_16 0x4107u /* (8,16) below-centroid */
107#define GC2_INTERIOR_1_1 0x043du /* (1,1) near apex */
108#define GC2_INTERIOR_30_0 0x03c1u /* (30,0) near G vertex */
109
110// -- GC3: 128x128 -- HW_VERIFIED
111#define GC3_V0_R 0x001fu /* apex pure R */
112#define GC3_TOP_X64 0x01efu /* (64,0) ~midpoint R->G */
113#define GC3_LEFT_Y64 0x3c0fu /* (0,64) ~midpoint R->B */
114#define GC3_INTERIOR_32_32 0x1cefu /* (32,32) interior */
115#define GC3_INTERIOR_1_1 0x001eu /* (1,1) - apex drift! R=30 not 31 */
116#define GC3_INTERIOR_64_32 0x1de7u /* (64,32) */
117#define GC3_INTERIOR_32_64 0x3ce7u /* (32,64) */
118#define GC3_INTERIOR_96_16 0x0ee3u /* (96,16) far-right interior */
119
120// --------------------------------------------------------------------------
121// GV: Vertical R-only gradient (probes left-edge color accumulator)
122// --------------------------------------------------------------------------
123//
124// Layout: v0=(0,0) R=31, v1=(N,0) R=0, v2=(0,N) R=0. Left edge carries
125// R going from 31 -> 0 across N rows. Probe column x=0.
126
127// -- GV3: H=W=3 -- HW_VERIFIED
128#define GV3_X0_Y0 0x001fu /* apex R=31 */
129#define GV3_X0_Y1 0x0014u /* R=20 */
130#define GV3_X0_Y2 0x000au /* R=10 */
131
132// -- GV5: H=W=5 -- HW_VERIFIED
133#define GV5_X0_Y0 0x001fu
134#define GV5_X0_Y1 0x0018u /* R=24 */
135#define GV5_X0_Y2 0x0012u /* R=18 */
136#define GV5_X0_Y3 0x000cu /* R=12 */
137#define GV5_X0_Y4 0x0006u /* R=6 */
138
139// -- GV7: H=W=7 -- HW_VERIFIED
140// Per-row 8-bit delta truncated; accumulator drift visible vs naive
141// (31, 27, 23, 19, 15, 11, 7) - hardware actually steps:
142// 31, 26, 22, 17, 13, 8, 4. Floor((R<<3)+accum)/8 at each row.
143#define GV7_X0_Y0 0x001fu /* R=31 */
144#define GV7_X0_Y1 0x001au /* R=26 */
145#define GV7_X0_Y2 0x0016u /* R=22 */
146#define GV7_X0_Y3 0x0011u /* R=17 */
147#define GV7_X0_Y4 0x000du /* R=13 */
148#define GV7_X0_Y5 0x0008u /* R=8 */
149#define GV7_X0_Y6 0x0004u /* R=4 */
150
151// -- GV11: H=W=11 -- HW_VERIFIED
152#define GV11_X0_Y0 0x001fu /* R=31 */
153#define GV11_X0_Y2 0x0019u /* R=25 */
154#define GV11_X0_Y4 0x0013u /* R=19 */
155#define GV11_X0_Y6 0x000eu /* R=14 */
156#define GV11_X0_Y8 0x0008u /* R=8 */
157#define GV11_X0_Y10 0x0002u /* R=2 */
158
159// --------------------------------------------------------------------------
160// GH: Horizontal R-only gradient (probes per-pixel-X color delta)
161// --------------------------------------------------------------------------
162//
163// Layout: v0=(0,0) R=31, v1=(N,0) R=0, v2=(0,N) R=31. Left edge stays
164// at R=31; top edge interpolates R 31->0 across N columns. Probe row
165// y=0. Hardware confirms GV / GH symmetry - identical values per N.
166
167// -- GH3 -- HW_VERIFIED
168#define GH3_Y0_X0 0x001fu
169#define GH3_Y0_X1 0x0014u
170#define GH3_Y0_X2 0x000au
171
172// -- GH5 -- HW_VERIFIED
173#define GH5_Y0_X0 0x001fu
174#define GH5_Y0_X1 0x0018u
175#define GH5_Y0_X2 0x0012u
176#define GH5_Y0_X3 0x000cu
177#define GH5_Y0_X4 0x0006u
178
179// -- GH7 -- HW_VERIFIED
180#define GH7_Y0_X0 0x001fu
181#define GH7_Y0_X1 0x001au
182#define GH7_Y0_X2 0x0016u
183#define GH7_Y0_X3 0x0011u
184#define GH7_Y0_X4 0x000du
185#define GH7_Y0_X5 0x0008u
186#define GH7_Y0_X6 0x0004u
187
188// -- GH11 -- HW_VERIFIED
189#define GH11_Y0_X0 0x001fu
190#define GH11_Y0_X2 0x0019u
191#define GH11_Y0_X4 0x0013u
192#define GH11_Y0_X6 0x000eu
193#define GH11_Y0_X8 0x0008u
194#define GH11_Y0_X10 0x0002u
195
196// --------------------------------------------------------------------------
197// GS: Saturation / vertex-exactness probes -- HW_VERIFIED
198// --------------------------------------------------------------------------
199//
200// GS_NEAR_MAX: apex R=31, base R=30. Apex pixel reads exactly R=31;
201// deep interior reads R=30 (the base color). No overshoot.
202//
203// GS_NEAR_MIN: apex R=0, base R=1. Apex reads R=0; deep interior reads
204// R=0 too - the 5-bit value R=1 lives at 8-bit R=8, but the
205// interpolated 8-bit value never crosses 8 at the probed interior
206// position (4,8). So a 1-LSB-in-5-bit triangle gets quantized away
207// for most of its area.
208//
209// GS_HALF_OF_LSB: apex R=1, base R=0. R=1 holds only at the apex pixel.
210// By y=2 (and below) the accumulator is below 5-bit threshold and
211// reads R=0. The per-row 8-bit delta is small enough that the 5-bit
212// truncation collapses the gradient to a single bright pixel.
213
214#define GS_NEAR_MAX_APEX 0x001fu
215#define GS_NEAR_MAX_INTERIOR 0x001eu
216#define GS_NEAR_MIN_APEX 0x0000u
217#define GS_NEAR_MIN_INTERIOR 0x0000u
218#define GS_HALF_OF_LSB_APEX 0x0001u
219#define GS_HALF_OF_LSB_Y2 0x0000u
220#define GS_HALF_OF_LSB_Y4 0x0000u
221#define GS_HALF_OF_LSB_Y6 0x0000u
222
223// --------------------------------------------------------------------------
224// GD: Dither overlay (E1[9] = 1) on the canonical 32x32 RGB triangle.
225// 4x4 OBS grid at (8..11, 8..11). -- HW_VERIFIED
226// --------------------------------------------------------------------------
227//
228// The dither table modulates per-pixel rounding direction in a 4-pixel
229// repeating pattern. Notice how adjacent pixels' R / G / B channels
230// flip by 1-2 LSBs in a stable spatial pattern - the Bayer-style
231// signature on real silicon.
232
233#define GD_8_8 0x1ceeu
234#define GD_9_8 0x212eu
235#define GD_10_8 0x1d2cu
236#define GD_11_8 0x216cu
237#define GD_8_9 0x250eu
238#define GD_9_9 0x210cu
239#define GD_10_9 0x254cu
240#define GD_11_9 0x214au
241#define GD_8_10 0x24ecu
242#define GD_9_10 0x292cu
243#define GD_10_10 0x252au
244#define GD_11_10 0x296au
245#define GD_8_11 0x2d0cu
246#define GD_9_11 0x290au
247#define GD_10_11 0x2d4au
248#define GD_11_11 0x2948u
249
250// --------------------------------------------------------------------------
251// GO: Vertex-order permutations of GC1. -- HW_VERIFIED
252// All six orderings produce identical interior pixels: the PS1
253// gouraud rasterizer is order-independent. Aliases inherit GC1 truth.
254// --------------------------------------------------------------------------
255
256#define GO_PERM_INTERIOR_2_2 GC1_INTERIOR_2_2
257#define GO_PERM_INTERIOR_1_3 GC1_INTERIOR_1_3