port: D397 — fix file-select/gun-barrel comb via RDP scanline-range snap in gfx_draw_rectangle

Root cause H2 confirmed: the gun-barrel / file-select vertical gradient is
built from 0.75-native-pixel-tall gSPTextureRectangle row strips (title2.c
(<<2)-1 bottom idiom). On the N64 RDP the scan converter floors the U10.2
edges to whole scanlines, so each strip rasterizes to exactly one scanline;
gfx_draw_rectangle passed the fractional edges straight to the GPU's
continuous rasterizer, aliasing the 0.75px pitch into a periodic 'comb'
(fixed ~3-native-row period; window period = 3*scale — confirmed by a
3-scale 640/1024/1280 GE_PCDUMP test).

Fix (port-layer, surgical): after the sub-pixel offset, floor the top edge to
its scanline and the bottom edge to the last covered scanline (the RDP
scanline range [top_scan, bot_scan+1]) before NDC conversion. No-op for
whole-pixel rects (edges already 4k/4k-1), so only sub-pixel strips change
and normal fills are untouched.

Also: remove the temporary GE_D397SH / D397SHM env-gated shader probes
(gfx_opengl.cpp, gfx_pc.cpp) now that the root cause is settled.

Docs: findings.md D397 -> RESOLVED (+3-scale confirmation + fix record),
findings-index.csv regenerated, porting-notes.md section D15 marked resolved
with the landed fix.

Verified by playtest: gun-barrel + file-select gradient render clean, comb
gone.
This commit is contained in:
James Dansereau committed 2026-09-27 22:42:43 -04:00
1 parent 2a60b8a912
commit a548fc4de6
5 files changed
+86 -51

No files matched your search

+1 -1
View File
@@ -273,7 +273,7 @@ D393,FIXED,Controller B/Y both crouch; no in-game use button —,FIXED + user li
D394,PARTIAL,Xbox 1.1 Jinx controller parity —,PARTIAL (port 1.1 gameplay button roles user-tested; exact gadget-category parity and other selectable presets outstanding; RB HD toggle unavailable).
D395,PARTIAL,Controller trapped in keyboard/mouse-only binding capture —,"PARTIAL (both options UIs block pad-initiated capture; pad B can cancel an active modal; NTSC build linked, controller-only retest owed)."
D396,PARTIAL,"F10 pad B/A used as slider decrement/increment, not back/select —","PARTIAL (B backs one F10 page, closes at root; A/X accept/toggle on edge but never shift a slider; D-pad/stick left/right still adjust; NTSC linked, pad retest owed)."
D397,OPEN,SELECT FILE background + intro gun-barrel show an interlaced/comb look —,"OPEN, re-scoped + root-causing (no fix yet):"
D397,RESOLVED,SELECT FILE background + intro gun-barrel show an interlaced/comb look —,RESOLVED (2026-09-28):
D398,FIXED,PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).,"SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 textMeasure arg-swap bug was found and fixed there)."
D399,PARTIAL,File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).,"LANDED (port-only, D343 class):"
D400,PARTIAL,"File-select ""PC Options"" label:","LANDED (port-only, frontoptions.c):"
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
273 D394
274 D395
275 D396
276 D397
277 D398
278 D399
279 D400
+12 -2
View File
@@ -681,7 +681,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D394 | **Xbox 1.1 Jinx controller parity** — user supplied the Xbox 1.1–1.4 control table; B is gadgets, stick clicks crouch, LB aim, RB HD toggle (not weapon cycling). | PARTIAL (port 1.1 gameplay button roles user-tested; exact gadget-category parity and other selectable presets outstanding; RB HD toggle unavailable). |
| D395 | **Controller trapped in keyboard/mouse-only binding capture** — pad could enter the binding modal but could not finish or cancel without keyboard. | PARTIAL (both options UIs block pad-initiated capture; pad B can cancel an active modal; NTSC build linked, controller-only retest owed). |
| D396 | **F10 pad B/A used as slider decrement/increment, not back/select** — B/Y fed Left and A/X fed Right on every row. | PARTIAL (B backs one F10 page, closes at root; A/X accept/toggle on edge but never shift a slider; D-pad/stick left/right still adjust; NTSC linked, pad retest owed). |
| D397 | **SELECT FILE background + intro gun-barrel show an interlaced/comb look** — D182(2) family, but present on FIRST display, not only after re-entry (2026-09-27). Full `## D397` entry at file tail. | OPEN, re-scoped + root-causing (no fix yet): data path proven clean (299 I8 row strips upload byte-exact vs the ground-truth PGM); the stale-RENDERTILE import hypothesis is REFUTED (D161 already routes CI+G_TT_NONE to the I8 import); MSAA, mips, noise dither, 5-bit quant, cache aliasing all ruled out. Leading cause H1: the file-select custom-CC LERP uses ENV_ALPHA 0x14 as the mix factor and the port feeds it as 20/255≈0.078 — a black texel then renders ≈0.92×prim, matching the measured 27/28/17 black-region census; the N64 RDP's 5-bit coefficient scaling is the open question (check `rsp/graphics/gmain.s` + the PD port's CC engine). Possible second defect H2: the 6-of-9-row comb in row-strip geometry. `GE_D397SH` shader probe in `gfx_opengl.cpp` (kept during investigation, remove before merge). An ineffective CI→I import-routing block was tried and removed (D161 covers it). |
| D397 | **SELECT FILE background + intro gun-barrel show an interlaced/comb look** — D182(2) family, but present on FIRST display, not only after re-entry (2026-09-27). Full `## D397` entry at file tail. | **RESOLVED (2026-09-28):** root cause = **H2** (confirmed by a 3-scale PCDUMP test — the comb period is fixed at ≈3 *native* rows across 640/1024/1280, i.e. window-period = 3×scale: the `(<<2)-1` 0.75-native-pixel row-strips rely on RDP floor-based scanline stepping that `gfx_draw_rectangle`'s continuous-float rasterization did not replicate). **Fix (port-layer, surgical):** `gfx_draw_rectangle` now floors the top edge to its scanline and the bottom edge to the last covered scanline (the RDP scanline range `[top,bot+1]`) before rasterizing — a no-op for whole-pixel rects, so only sub-pixel strips change; §D15 in `docs/porting-notes.md` documents the mechanism. H1 (ENV_ALPHA 5-bit CC scaling) REFUTED by a byte-identical PD-port cross-check (the darkening it predicted is faithful, not a bug). `GE_D397SH`/`D397SHM` probes removed. Verified by playtest: gun-barrel + file-select gradient render clean, comb gone. |
| D398 | **PC Options label: D348's procedural slider glyph replaced with an existing mainfolderimages texture (2026-09-27).** Full `## D398` entry at file tail. | SUPERSEDED by D400 (icon removed, text-only label; purple-hover bug moot; the D400 `textMeasure` arg-swap bug was found and fixed there). |
| D399 | **File-select bottom row (SELECT FILE text + Copy/Erase + PC Options dot/label) shifted 15px left as a block (user sign-off 2026-09-27).** Full `## D399` entry at file tail. | LANDED (port-only, D343 class): `selectpos.f[0]` 110→95 (new `#ifdef PORT` in front.c), Copy 200/222→185/207, Erase 282/304→267/289 (D343 PORT block), `LABEL_X` 358→343 (`frontoptions.c`); hit bounds recompute from the shifted positions. Clean build, PCDUMP frame 195 no overlaps; by-eye check owed. |
| D400 | **File-select "PC Options" label: icon removed, text-only; fixed a swapped textMeasure() arg bug and a 7px vertical misalignment vs Copy/Erase (user sign-off 2026-09-27).** Full `## D400` entry at file tail. | LANDED (port-only, `frontoptions.c`): D398's dot icon dropped on user instruction ("most straightforward design"); label now a plain `textRender` word, GAP 28 after Erase. Two measurement bugs fixed: (1) `textMeasure(textheight, textwidth)` args passed swapped -> label 35px above the bar, hot band transposed; (2) bare-label height measures 0 (vs 14 for newline-terminated Copy/Erase) -> 7px lower tops; fixed by restoring the `kLabelNL` height measure. PCDUMP frame 195: tops aligned 279.8 vs 279.3, 31px clearance from Erase; by-eye check owed. |
@@ -15177,13 +15177,23 @@ I logged all of this as **D397** (`docs/dev/findings.md`, index CSV, and the D18
**Status (2026-09-28, per `scratch/d397_fs/HANDOFF-D397.md`) — OPEN, re-scoped; no fix landed.** An in-tree CI→I import-routing block in `gfx_pc.cpp` `import_texture` was tried and is **ineffective** (the D116 stale-tile-metadata hypothesis is REFUTED: D161 already routes `fmt==CI && palette==G_TT_NONE` to `import_texture_i8`; the GE_TEXI logs show `palfmt=0` on all 299 rows) — the block was removed 2026-09-28. Established: the data path is 100% clean (299 row strips upload byte-exact vs `scratch/d397_fs/gunbarrel.pgm`); the artifact is static; MSAA, mips, RAND_NOISE, 5-bit quant and texture-cache aliasing are ruled out. Leading hypotheses: **H1** — the file-select custom-CC LERP (`gDPSetCombineLERP(TEXEL0, PRIMITIVE, ENV_ALPHA, ...)`) uses ENV_ALPHA 0x14 as the mix factor; the port feeds it as `20/255 ≈ 0.078` (`G_CCMUX_ENV_ALPHA` input, `gfx_pc.cpp`), so a black texel renders ≈0.92×prim ≈ the measured 27/28/17 black-region census — the N64 RDP 5-bit coefficient scaling (check `rsp/graphics/gmain.s` and the PD port's CC engine for the same combine id) is the root-cause question; **H2** — the 6-of-9-row comb is a second, separate row-strip-geometry defect (non-integer quad y + NEAREST, or the 256-tri vertex-ring wrap between the two tris of a row); **H3** — part of the darkened look is authentic N64 (G_TC_FILT 4-bit intensity dither, unimplemented here). A `GE_D397SH` env-gated shader-formula probe (in `gfx_opengl.cpp`, ~30 lines) stays in-tree until the root cause lands, then is removed. Next steps per the handoff: Q1 correlate which of the 20 compiled shaders is the gun-barrel row-strip one, Q4 settle the LERP-factor scaling against gmain.s/PD, Q5 native 640×480 FBO capture to re-measure the comb period.
**Status (2026-09-27b, static read against the local `pd_port` checkout) — H1 REFUTED, H2 promoted to leading cause.** Cross-checked `G_CCMUX_ENV_ALPHA` handling against `C:\Users\james\Source\Repos\pd_port\port\fast3d\gfx_pc.cpp`: byte-identical formula (`tmp.r=tmp.g=tmp.b=rdp.env_color.a`, then `/255.0f` at the vertex-color pack site) — no special-cased coarser scaling in either port. There is no "5-bit RDP coefficient" quirk in the real color combiner; PRIM/ENV alpha are genuine 8-bit `gDPSetEnvColor` fields and the combiner's A/B/C/D math is full 8-bit on real hardware (the 5-bit-precision idea conflated the CC with an unrelated hardware unit). **H1 is very likely not the (or not the only) cause; the uniform darkening it predicts may simply be faithful/authored** — do not correct it without an N64/1964-GEPD reference frame.
Found a concrete geometric mechanism for **H2**, promoted to leading cause: `titleRenderFolderMenuBackgroundLines` (`src/game/title2.c:24`) emits `lry - uly == 3` (U10.2 units) per row — a **0.75-native-pixel-tall** rect, not 1.0 — via the universal N64 `((y+1)<<2)-1` idiom. That idiom is only exact on real hardware because the RDP's scan converter *floors* the fixed-point Y to an integer scanline before testing coverage. `gfx_draw_rectangle` (`gfx_pc.cpp:3443`) does not replicate that: it maps the U10.2 corners straight to continuous NDC floats and rasterizes with the GPU's ordinary continuous coverage rules. A sub-1-native-pixel-tall rect stacked 299 times (each offset exactly 1.0 native px from the last) does not reliably rasterize as "the one scanline the idiom intended" under continuous rasterization — which of the two neighbouring device rows a given source row's fractional edges land in drifts as the stack accumulates, worsened by any non-integer native→window scale (`gfx_adjust_viewport_or_scissor`'s `ratioY`). This is a rasterization property of the geometry, not stale state, so it naturally explains the first-render (non-re-entry) trigger that refuted D182's hypothesis for this instance. Full mechanism + tell for future occurrences: `docs/porting-notes.md` §D15. **Not yet done: a live capture to confirm the comb period tracks `ratioY`** (predicted: comb period should change if the window/render resolution changes, at fixed native resolution) — this is the concrete falsifiable test before attempting a fix. No code changed this pass (diagnosis only).
**2026-09-28 progress (this session — D397SH/D397SHM probes, env-gated in `gfx_opengl.cpp`/`gfx_pc.cpp`):**
- **Q1 answered:** the gun-barrel row-strip shader is `id0=0000000010001218`, formula `texel = mix(vInput1, texVal0.rgb, vInput2)`, with (D397SHM) **vInput1 = G_CCMUX_PRIMITIVE, vInput2 = G_CCMUX_ENV_ALPHA**, so the LERP (front.c:2596 `gDPSetCombineLERP(TEXEL0, PRIMITIVE, ENV_ALPHA, PRIMITIVE, …)`) renders `out = PRIM + (TEXEL − PRIM)·(envA/255)`; black texels → `prim·(1 − envA/255)`. The 5-bit LERP coef-C in the packed combine word is 0x14 (= 20, i.e. the env-alpha value travels in the coef slot).
- **H1's "1/255 vs N64 1/32 coefficient" variant is effectively ruled out:** the PD ground-truth port feeds the *same* `env_color.a / 255.0` factor for the identical ENV_ALPHA LERP input (`pd_port/port/fast3d/gfx_pc.cpp` ~1548→1580; same `append_formula`/`mix` generation, same 5-bit-coef packing at :1949). The GE port matches the reference implementation, so the LERP factor is **not** a port-side scaling bug. (If the *N64 hardware* itself scaled the 5-bit coefficient differently, that would be a shared PD/GE port interpretation, not a GE regression — and it cannot explain a *periodic* pattern regardless.)
- **The visible defect is periodic (the 6-of-9-row comb), which a uniform LERP factor cannot produce** → the comb is **H2 (row-strip geometry / vertex-ring)**, not H1. The gray census (27/28/17) is the LERP factor applied on top; the *lines* are H2.
- **Next steps (next session):** (1) extend the probe / add an env-gated counter in `gfx_flush()` to test Q3 (does a 256-tri ring flush land between the two tris of a row strip, splitting them across draw calls with attribute-state drops?); (2) Q5 native 640×480 FBO capture to re-measure the comb period in native rows (expect ≈3 if it's the 40px-window period scaled); (3) H3 control — an N64/PD screenshot of the equivalent screen to bound how much of the darkened look is authentic.
- **Cleanup owed before merge:** remove the `GE_D397SH` probes in `gfx_opengl.cpp` (formula dump) and `gfx_pc.cpp` (D397SHM mapping dump).
- **Cleanup owed before merge:** remove the `GE_D397SH` probes in `gfx_opengl.cpp` (formula dump) and `gfx_pc.cpp` (D397SHM mapping dump) — **done 2026-09-28** (both blocks removed; see below).
**RESOLVED (2026-09-28) — H2 confirmed; fix landed in `gfx_draw_rectangle`.**
- **The falsifiable 3-scale test CONFIRMS H2.** Captured the file-select background at 640×480 (1.0×), 1024×768 (1.6×) and 1280×960 (2.0×) via `GE_PCDUMP` and measured the comb period in *native* rows (window-period ÷ scale, on a clean background band, detrended diff-autocorrelation): **s2 (1.6×) P_win=5 → P_nat≈3.1; s3 (2.0×) P_win=6 → P_nat=3.0 (two independent bands agree); s1 (1.0×) too fine to resolve (near-Nyquist, consistent).** A *fixed native period* (~3 rows) is exactly the beat of 0.75-px strips stacked at a 1.0-px pitch (0.75 = 3/4 → four strips span three native rows → a 3:4 beat). A period that scales with the window while staying fixed in native rows is the signature of a **fixed-point scan-converter flooring** artifact — not a LERP/CC factor (which would be scale-invariant and non-periodic). H2 confirmed.
- **Fix (port-layer, surgical, `gfx_pc.cpp` `gfx_draw_rectangle`):** after the sub-pixel offset is applied, floor the U10.2 top edge to its scanline and the bottom edge to the *last covered* scanline — the RDP scanline range `[top_scan, bot_scan+1]` — before rasterizing: `top_scan = uly>>2`, `bot_scan = (lry-1)>>2`, then `uly = top_scan<<2`, `lry = (bot_scan+1)<<2`. **No-op for whole-pixel rects** (their edges are already `4k`/`4k−1`, and the N64 `(<<2)−1` bottom idiom already yields `bot_scan` = the intended last row), so only sub-pixel-tall strips change — exactly the gun-barrel / file-select row strips. Mechanism + future-occurrence tell: `docs/porting-notes.md` §D15.
- **Verified by playtest:** the gun-barrel intro + file-select gradient render clean — the interlaced/comb look is gone (user-confirmed on the v0.4.0 playtest). `GE_D397SH`/`D397SHM` probes removed.
- **H3 note:** the *overall* darkened look (gray census 27/28/17) is the faithful LERP (H1, byte-identical to the PD port) and/or authored — **not** a defect. Only the periodic comb was the bug, and it is now fixed; do not "correct" the darkening.
## D398 — PC Options label: IMG_DOT texture replaces the D348 procedural slider glyph (2026-09-27)
+51
View File
@@ -1295,6 +1295,57 @@ GE on N64 renders gameplay at ~20–30 fps, so `g_ClockTimer` (= `speedgraphfram
- **Repro correctly:** force the granularity with `GE_D318B_CLK=N` (TEMP probe, lockstep N ticks/frame) and A/B N=1 vs N=2. Wall-clock runs on a slow host (WSLg renders ~5 fps → mixed clk 1–6) and `GE_DETERM=1` (advances per VI-retrace request, also mixed clk) both give uncalibrated results.
- **Where else to look:** any `for/while (numticks)` accumulator followed by a single threshold/floor consumer; D193 (AI locomotion rate) and D243 (cutscene `numticks` bursts) touched the same axis from the other side.
## D15. A texture-rectangle idiom that is sub-1-native-pixel tall relies on the RDP's floor-based fixed-point scanline stepping — now replicated in `gfx_draw_rectangle` (D397, RESOLVED 2026-09-28)
GE's front-end vertical-gradient trick (`titleRenderFolderMenuBackgroundLines`,
`src/game/title2.c`) draws a tall image as 299 separate 1-scanline-tall
`gSPTextureRectangle`s, one per source row, each tinted by a per-row
interpolated `gDPSetPrimColor`. The lower-right Y coordinate uses the
universal N64 idiom `((y+1)<<2)-1`, which in U10.2 units is **3, not 4** — a
0.75-native-pixel-tall rect, not 1.0. This is correct *on real hardware*
specifically because the RDP's scan converter steps by truncating
(flooring) the fixed-point Y to an integer scanline before testing coverage,
so `y*4+3` still falls in scanline `y` and nothing else — the "-1" is a
hardware-truncation idiom, not a literal float height.
`gfx_pc.cpp`'s `gfx_draw_rectangle` does not replicate that truncation: it
converts the U10.2 corners straight to continuous NDC floats
(`ulyf/lryf = ±(y / (4·HALF_SCREEN_HEIGHT)) + 1`) and hands them to the GPU's
own continuous rasterizer, which uses pixel-center/coverage rules, not
floor-stepped scanlines. A rect that is 0.75 (native) pixels tall, stacked
299 times with each row offset by exactly 1.0 native pixel from the last,
does not reliably rasterize as "exactly the one scanline the N64 idiom
intended" under a continuous rasterizer — whether a given row rounds to 0 or
1 device pixel depends on where its fractional edges land relative to the
device pixel grid, and that phase drifts row-to-row (worse under any
non-integer native→window viewport scale, `gfx_adjust_viewport_or_scissor`'s
`ratioX`/`ratioY`). Stacked over ~300 rows filling the screen, sporadic
per-row rounding reads as a periodic horizontal comb/interlace — this is the
leading mechanism for D397's "interlaced" file-select background and intro
gun-barrel, and is a **first-render, not re-entry-triggered** artifact
(unlike D182), because it is a rasterization property of the geometry itself,
not stale state.
**Tell:** any GE front-end/menu draw that fakes a large image or gradient by
stacking many independent 1-scanline (or otherwise sub-1-native-pixel-tall)
`gSPTextureRectangle`s rather than one multi-row texture — the `(<<2)-1`
lower-right idiom is the signature to grep for. A single such rect is
invisible (sub-pixel rounding on one edge); many stacked ones make the
rounding pattern visible as banding.
**Fix (landed 2026-09-28, RESOLVED):** `gfx_draw_rectangle` (`gfx_pc.cpp`) now replicates the RDP truncation. After the sub-pixel offset is applied, it floors the U10.2 top edge to its scanline and the bottom edge to the *last covered* scanline — the RDP scanline range `[top_scan, bot_scan+1]` — before converting to NDC: `top_scan = uly>>2`, `bot_scan = (lry-1)>>2`, then `uly = top_scan<<2`, `lry = (bot_scan+1)<<2`. This is a **no-op for whole-pixel rects** (their edges are already `4k`/`4k−1`, and the `(<<2)−1` bottom idiom already yields `bot_scan` = the intended last row), so only sub-pixel-tall strips change. **Confirmed by a falsifiable 3-scale PCDUMP test:** the comb period is fixed at ≈3 *native* rows across 640/1024/1280 (window-period = 3×scale) — the beat of 0.75-px strips at a 1.0-px pitch — exactly the fixed-point-flooring signature, ruling out the scale-invariant LERP/CC factor. Verified by playtest: gun-barrel + file-select gradient render clean, comb gone.
**Not the cause (checked and ruled out for D397):** the CC/LERP scaling of
`ENV_ALPHA` as `value/255.0f` (`gfx_pc.cpp` around the `G_CCMUX_ENV_ALPHA`
case) is byte-identical to the Perfect Dark PC port's (`gfx_pc.cpp` in the
local `pd_port` checkout) — same formula, no special-cased "5-bit RDP
coefficient" scaling in either port. The real RDP color combiner's A/B/C/D
inputs are 8-bit values (PRIM/ENV alpha come from an 8-bit `gDPSetEnvColor`
field); there is no coarser-precision quirk to correct here. A uniform
darkening from a nonzero `ENV_ALPHA` LERP factor is very likely faithful
(possibly authored/original-dev-intentional tinting), not a bug — do not
"fix" it without an N64/1964-GEPD reference screenshot proving otherwise.
## E. Process / method notes
- **Never `exit()` while another thread may be inside the GL driver (D344).**
-30
View File
@@ -560,36 +560,6 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad
vs_buf[vs_len] = '\0';
fs_buf[fs_len] = '\0';
#ifdef PORT
/* D397 TEMP probe (env-gated): dump the generated texel formula per
* shader so the file-select gunbarrel combine can be inspected. */
{
static int d397_on = -1;
if (d397_on < 0) d397_on = getenv("GE_D397SH") != NULL;
if (d397_on) {
fprintf(stderr,
"D397SH: id0=%016llx id1=%08x 2cyc=%d alpha=%d noise=%d edge=%d blur=%d grey=%d "
"c0r=(%u,%u,%u,%u) c0a=(%u,%u,%u,%u) c1r=(%u,%u,%u,%u) c1a=(%u,%u,%u,%u) "
"single(%d,%d/%d,%d) mult(%d,%d/%d,%d) mix(%d,%d/%d,%d)\n",
(unsigned long long)shader_id0, (unsigned)shader_id1,
(int)cc_features.opt_2cyc, (int)cc_features.opt_alpha, (int)cc_features.opt_noise,
(int)cc_features.opt_texture_edge, (int)cc_features.opt_blur, (int)cc_features.opt_grayscale,
cc_features.c[0][0][0], cc_features.c[0][0][1], cc_features.c[0][0][2], cc_features.c[0][0][3],
cc_features.c[0][1][0], cc_features.c[0][1][1], cc_features.c[0][1][2], cc_features.c[0][1][3],
cc_features.c[1][0][0], cc_features.c[1][0][1], cc_features.c[1][0][2], cc_features.c[1][0][3],
cc_features.c[1][1][0], cc_features.c[1][1][1], cc_features.c[1][1][2], cc_features.c[1][1][3],
(int)cc_features.do_single[0][0], (int)cc_features.do_single[0][1],
(int)cc_features.do_single[1][0], (int)cc_features.do_single[1][1],
(int)cc_features.do_multiply[0][0], (int)cc_features.do_multiply[0][1],
(int)cc_features.do_multiply[1][0], (int)cc_features.do_multiply[1][1],
(int)cc_features.do_mix[0][0], (int)cc_features.do_mix[0][1],
(int)cc_features.do_mix[1][0], (int)cc_features.do_mix[1][1]);
const char* tf = strstr(fs_buf, "texel = ");
if (tf) fprintf(stderr, "D397SHF: %s;\n", tf);
}
}
#endif
const GLchar* sources[2] = { vs_buf, fs_buf };
const GLint lengths[2] = { (GLint)vs_len, (GLint)fs_len };
GLint success;
+22 -18
View File
@@ -550,24 +550,6 @@ static void gfx_generate_cc(struct ColorCombiner* comb, const ColorCombinerKey&
comb->used_textures[1] = used_textures[1];
// comb->prg = gfx_lookup_or_create_shader_program(shader_id0, shader_id1);
memcpy(comb->shader_input_mapping, shader_input_mapping, sizeof(shader_input_mapping));
#ifdef PORT
/* D397 TEMP probe (env-gated, remove before merge): dump which runtime
* G_CCMUX inputs this combine feeds as vInput1..N so the file-select
* LERP factor source can be settled against the measured census. */
if (getenv("GE_D397SH")) {
fprintf(stderr,
"D397SHM: id0=%016llx cmode=0x%016llx map0=[%u %u %u %u %u %u %u] map1=[%u %u %u %u %u %u %u]\n",
(unsigned long long)comb->shader_id0, (unsigned long long)key.combine_mode,
(unsigned)comb->shader_input_mapping[0][0], (unsigned)comb->shader_input_mapping[0][1],
(unsigned)comb->shader_input_mapping[0][2], (unsigned)comb->shader_input_mapping[0][3],
(unsigned)comb->shader_input_mapping[0][4], (unsigned)comb->shader_input_mapping[0][5],
(unsigned)comb->shader_input_mapping[0][6],
(unsigned)comb->shader_input_mapping[1][0], (unsigned)comb->shader_input_mapping[1][1],
(unsigned)comb->shader_input_mapping[1][2], (unsigned)comb->shader_input_mapping[1][3],
(unsigned)comb->shader_input_mapping[1][4], (unsigned)comb->shader_input_mapping[1][5],
(unsigned)comb->shader_input_mapping[1][6]);
}
#endif
}
static struct ColorCombiner* gfx_lookup_or_create_color_combiner(const ColorCombinerKey& key) {
@@ -3471,6 +3453,28 @@ static void gfx_draw_rectangle(int32_t ulx, int32_t uly, int32_t lrx, int32_t lr
uly += rdp.subpixel_ofs_y;
lry += rdp.subpixel_ofs_y;
// D397: the N64 RDP floors the U10.2 top/bottom edges of a texture rect to
// whole scanlines before its per-scanline coverage test, so a rect covers
// scanlines [floor(uly/4), floor((lry-1)/4]]. A rect whose height is not a
// whole number of native pixels therefore rasterizes to a whole number of
// scanlines on hardware at every window scale. The gun-barrel / file-select
// gradient is built from 0.75px-tall row strips (title2.c: uly=(i+12)<<2,
// lry=((i+13)<<2)-1 -> 0.75px tall at 1px pitch); on the RDP each strip is
// exactly one scanline. The continuous-NDC conversion below instead passes
// the fractional bottom edge straight to the GPU, whose coverage test
// aliases the 0.75px pitch into a visible "comb" (H2: period fixed in
// native rows, ~3 = the 0.75px-at-1px-pitch beat, window period = 3*scale).
// Snap the top/bottom edges to the RDP scanline range so a sub-pixel strip
// rasterizes to exactly the scanline it targets. No-op for whole-pixel
// rects (edges already multiples of 4) and for the copy-mode +1<<2 edge
// (already a whole scanline), so normal fills are unchanged.
if (lry > uly) {
const int32_t top_scan = uly >> 2;
const int32_t bot_scan = (lry - 1) >> 2;
uly = top_scan << 2;
lry = (bot_scan + 1) << 2;
}
// U10.2 coordinates
float ulxf = ulx;
float ulyf = uly;