From 327e6fe1a7888216f5ee7843b9b80f35e5e968e8 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Mon, 21 Sep 2026 06:00:28 -0400 Subject: [PATCH] D75 (M-196): refute slot-overwrite hypothesis; narrow logo bug to texture-modulate render path New env-gated GE_D75D probe (gfx_pc.cpp) captures per-frame V/T token stream + per-triangle rejection/emission accounting for the front-end logo window. Findings (bare front-end boot, logo = frames ~244+, nv=93/nt=1018): - Slot-overwrite (M-195's leading hypothesis) REFUTED: the DL is streamed (V TTT...VTTT interleaved), max triangle vertex index = 15, so no cross-batch vertex clobbering can occur. - Rejection gates are not the cause: of 1018 tris/frame, trivial-reject=0, backface-cull ~520 (normal half), ~500 reach GL, all on-screen (NDC in [-1,1]^2). Not the invisible flag; vertex colors [0,255]; depth normal. - PPM analysis: logo window renders 0-33 content px/frame (the 'tiny fragment'), vs title quads at 8.36% coverage. - Narrowed cause: combine mode 0x009ffe4f19ffe4f1 parses to rgb a=TEXEL0 c=SHADE (texture-modulate); gfx_generate_cc sets used_textures[0]=1, but the material still renders black-on-black. Next step: trace where the TEXEL0 sampler is lost between combiner build and draw (shader_id0 -> GL program / prg[tm] / texture upload). Port-layer fast3d only; no rule-2 sign-off needed. Probes env-gated, inert without GE_D75D (verified: 0 lines in a normal run). --- docs/dev/GE-ENV-PROBES.md | 3 +- docs/dev/findings.md | 2 + port/fast3d/gfx_pc.cpp | 160 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 1 deletion(-) diff --git a/docs/dev/GE-ENV-PROBES.md b/docs/dev/GE-ENV-PROBES.md index deea09d4..7264b143 100644 --- a/docs/dev/GE-ENV-PROBES.md +++ b/docs/dev/GE-ENV-PROBES.md @@ -3,7 +3,7 @@ Every `getenv("GE_…")` in the tree. The table prose is hand-curated; **`tools_pc/gen_env_probes.py`** re-greps the live sites and reports drift (NEW / GONE vars + a fresh file:line map) — run it before trusting the -File:line cells. Last reconciled 2026-09-21 (GE_D235 added, D235 fix wired; GE_D288 refined per M-155 follow-up; GE_D75V added for the Nintendo-logo vertex-slot investigation, M-195; GE_D322 pool-telemetry probe added). All are **env-gated**: unset = zero behavior +File:line cells. Last reconciled 2026-09-21 (GE_D235 added, D235 fix wired; GE_D288 refined per M-155 follow-up; GE_D75V added for the Nintendo-logo vertex-slot investigation, M-195; GE_D322 pool-telemetry probe added; GE_D75D + D75DCC added for the M-196 draw-path investigation that refuted slot-overwrite). All are **env-gated**: unset = zero behavior change, so they are safe to leave in a release build (they only cost a `getenv` on the guarded path). "Dead" below means *the finding it was built for is closed* — the probe is strip-candidate scaffolding, not that it does @@ -73,6 +73,7 @@ Two classes: | `GE_D252` | `port/fast3d/gfx_pc.cpp:733` (`import_texture`, cached `s_d252`) | D252 rainbow-particle diag: one-shot per (address,size) import log of the tile state (tmem/fmt/siz/addr/size/line) for small textures so a suspect particle tile can be checked against its source bytes by hand. Remove once D252 is resolved. | **live** (D252 OPEN — non-reproducible) | | `GE_D235` | `src/boss.c` (per-stage `while (!done)` block, after `memaReset`; `#ifdef PORT`) | D235 evidence probe: logs how many GL-texture-cache entries the per-stage `videoResetTextureCache()` call purges at each level load. A cold boot into a level should log 0; a death/restart (or any transition from a level that uploaded textures) logs >0 — direct evidence of the stale-entry mechanism. The clear itself is **not** gated on this var (it's the fix, see below); the var only controls the log line. | **live** (D235) | | `GE_D75V` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex`, end of the copy/transform loop; cached env check) | D75 (Nintendo logo) per-batch vertex probe: for each `G_VTX` batch in frames `lo..hi`, logs frame#, source pointer, n_vertices, original dest_index, raw ob[0..2] of the first vertex, min/max of the transformed x/y/z/w across the batch, and the MP translation row. Used to prove the transform is correct (M-195) and that every batch encodes v0=0 (slot-overwrite hypothesis). Value form `"lo-hi"` (e.g. `300-900` = the logo screen); unset = off. | **live** (D75) | +| `GE_D75D` | `port/fast3d/gfx_pc.cpp` (`gfx_sp_vertex` G_VTX case + `gfx_sp_tri1` rejection/emit gates; cached env check) | D75 (Nintendo logo) per-frame draw-path probe (M-196): for frames `lo..hi`, logs one line/frame with the V/T token stream (`seq`), max triangle vertex index, cumulative vertices loaded, and per-triangle accounting — trivial-reject / backface-cull counts, how many tris reach GL, how many of those are on-screen (NDC bbox ⊂ [−1,1]²), invisible-flag count, vertex-color min/max, first oml word, depth z/w range + NaN count, texture-sampler presence (`utex` = GL shader, `cutex` = combiner), combine mode, prim/env color. Used to REFUTE the slot-overwrite hypothesis (DL is streamed, max index 15) and narrow the bug to a texture-modulate material rendering black. Value form `"lo-hi"` (logo window ≈ `244-320` on a bare front-end boot); unset = off. Companion one-shot `D75DCC:` log in `gfx_generate_cc` fires when `combine_mode==0x009ffe4f19ffe4f1` to decode the logo's combine slots. | **live** (D75) | ## Dxx diagnostic probes — finding closed unless noted diff --git a/docs/dev/findings.md b/docs/dev/findings.md index 17f218b4..fead5b52 100644 --- a/docs/dev/findings.md +++ b/docs/dev/findings.md @@ -2428,6 +2428,8 @@ Confidence overall: **medium** (triage solid, no runtime confirmation). > **M-195 UPDATE (v0.4.0 Phase 1, 2026-09-21) — Nintendo logo: vertex transform proven CORRECT; leading hypothesis now = G_VTX slot overwrite.** Three-pass investigation this session: **(1) Emission is fine.** Fresh `GE_D63=1` bare-front-end run (log `/c/tmp/d75_d63.log`, 32k lines): all 23 nintendologo Primary GDLs are emitted **every frame** of the logo screen (~1002 hits each, BaseAddr=0x70157a98), and the log contains **zero** `D146: unknown GBI opcode` lines — the software RSP consumes every DL to completion. PC-side GDLs verified byte-faithful to the N64 source via the d43 toolchain (same custom GE opcode sequences, same vertex counts). **(2) Transform is fine.** New `GE_D75V="lo-hi"` probe in `gfx_sp_vertex` (per-batch transformed-vertex min/max + MP translation row; uses new `videoGetFrameCount()` accessor) shows the logo's vertices land exactly where the wordmark geometry says they should: x∈[−2500,+2500], y∈[±644], w≈3.9k–5k, MP translation (0,0,3878.7) matching `lookat(0,0,4000)` — this eliminates basematrix, the f32→s32 conversion loop, and the matrix-list suspects. **(3) Leading hypothesis: vertex-slot overwrite.** Every `G_VTX` batch in these GDLs encodes **v0=0** (fast3d decodes `n=C0(0,16)/sizeof(Vtx)`, `dest_index=C0(16,4)`; probe confirms di=0 for every batch), so a node whose vertices span multiple 16-vertex batches writes all of them to slots 0..n−1 of the shared buffer — each later batch **overwrites** the earlier ones and only the last batch survives. Triangles referencing indices beyond the last batch's count read stale slots from prior nodes (likely clipped, not visible as junk — consistent with the observed single ~25×6px orbiting fragment matching the smallest node's 4-vert quad at x≈1020 → ~155px orbit radius vs observed ~200px). The N64 RSP must handle multi-batch nodes differently (per-batch ring or relative indexing) — GE's own ucode is not in this repo, so the next step is empirical: dump one full node's GDL stream from the ROM and cross-reference its `G_TRI4`/`G_TRI1` index ranges against per-batch vertex counts. If confirmed, the fix is port-layer only (fast3d vertex-slot management) — no rule-2 sign-off needed. Probe registered in `GE-ENV-PROBES.md` as `GE_D75V`. +> **M-196 UPDATE (v0.4.0 Phase 1, 2026-09-21) — slot-overwrite hypothesis REFUTED; bug narrowed to the color-combiner/texture path.** New `GE_D75D="lo-hi"` probe in `port/fast3d/gfx_pc.cpp`: per-frame G_VTX/G_TRI token stream + per-triangle rejection/emission accounting (trivial-reject, backface-cull, NDC bbox, invisible-flag, vertex-color range, oml, depth z/w, combine mode, prim/env color). Front-end boot (no `-level`), logo window = frames ~244+ where a large model appears (nv=93 G_VTX batches, **nt=1018** tris/frame; the earlier frames 5–243 are the title-screen textured quads, nv=2/nt=12, which render fine at 8.36% coverage). **(1) Slot-overwrite is refuted.** The logo model's DL is **streamed**: `seq` = `VTTTT…VTTT…` (each G_VTX batch immediately followed by its own triangles), and `maxtri=15` — no triangle ever references a vertex index beyond 15, i.e. within a single 16-slot batch. M-195's "all batches write to slot 0, only the last survives" mechanism **cannot apply** to this model; there is no cross-batch clobbering. **(2) Rejection gates are not the cause.** Of the 1018 tris/frame: `rej_triv=0`, `rej_cull≈520` (a normal ~half for a closed mesh), **~500 reach OpenGL**; and all ~500 are on-screen (`emit_on≈500, emit_off=0`, NDC bbox ⊂ [−1,1]²). Not the invisible flag (`invis=0`). Vertex colors span `[0,255]` (not black). Depth `z/w ∈ [−1, 0.97]`, no NaN. **(3) Symptom quantified.** PPM pixel analysis of the logo window: **0–33 content px/frame** (a ~19×2px fragment right of centre) vs the title quads' 8.36% — this is M-195's "tiny orbiting fragment", now measured. **(4) Narrowed cause = texture-modulate material rendering black.** The model's combine mode is `0x009ffe4f19ffe4f1` (`is_2cyc=0`). A one-shot decode in `gfx_generate_cc` (new `D75DCC:` log, gated to this exact word) shows it parses to **rgb a=`G_CCMUX_TEXEL0`(1), c=`G_CCMUX_SHADE`(4)** — a texture-modulate material — and `gfx_generate_cc` sets `used_textures[0]=1`. `env=(0,0,0,0)`, `prim=(255,255,255,255)`. Intended output = TEXEL0 × SHADE; if TEXEL0 samples black at draw time (no/empty texture bound), the whole model is black-on-black → invisible except AA-edge fragments. **Anomaly to close next:** at emit time BOTH `gfx_rapi->shader_get_info(prg)` and `comb->used_textures[0]` read **0**, even though `gfx_generate_cc` set it to 1 for this exact combine_mode — so the TEXEL0 input is being lost between combiner build and draw (suspects: `shader_id0`→`gfx_lookup_or_create_shader_program`, the `prg[tm]` variant selection at gfx_pc.cpp:~2503, or the GL backend's sampler binding / texture upload for this model's tile). If the TEXEL0 sampler is dropped, `SHADER_TEXEL0` defaults to 0 → MODULATE → black. Port-layer fast3d only; no rule-2 sign-off needed. Probes (`GE_D75D`, `D75DCC`) registered in `GE-ENV-PROBES.md`; both env-gated/inert without the var. + **D75 Bug 2 — RUNTIME PROBE (session M-32b, `GE_D75=1` in `title.c` `sub_GAME_7F007F30`, kept env-gated).** Booted the bare front end (`GE_D75=1 GE_PCDUMP="1-1200:120"`, no `-level`) and captured through the diff --git a/port/fast3d/gfx_pc.cpp b/port/fast3d/gfx_pc.cpp index 892a8743..b262f8a0 100644 --- a/port/fast3d/gfx_pc.cpp +++ b/port/fast3d/gfx_pc.cpp @@ -353,6 +353,9 @@ static const char* acmux_to_string(uint32_t acmux) { return tbl[acmux]; } +/* forward decl for the env-gated D75D probe (defined further down) */ +static bool d75d_env_active(void); + static void gfx_generate_cc(struct ColorCombiner* comb, const ColorCombinerKey& key) { bool is_2cyc = (key.options & (uint64_t)SHADER_OPT_2CYC) != 0; @@ -482,6 +485,17 @@ static void gfx_generate_cc(struct ColorCombiner* comb, const ColorCombinerKey& } } } + /* TEMP D75D: one-shot decode of the logo model's combine mode (env-gated via d75d_lo) */ + { + static int d75d_cc_once = 0; + if (!d75d_cc_once && d75d_env_active() && key.combine_mode == 0x009ffe4f19ffe4f1ULL) { + d75d_cc_once = 1; + fprintf(stderr, "D75DCC: comb=0x%016llx is_2cyc=%d c0=(rgb a=%u b=%u c=%u d=%u)(al a=%u b=%u c=%u d=%u) utex0=%d\n", + (unsigned long long)key.combine_mode, (int)is_2cyc, + c[0][0][0], c[0][0][1], c[0][0][2], c[0][0][3], + c[0][1][0], c[0][1][1], c[0][1][2], c[0][1][3], (int)used_textures[0]); + } + } { uint8_t input_number[16] = { 0 }; int next_input_number = SHADER_INPUT_1; @@ -1444,6 +1458,138 @@ static inline bool fast3d_ptr_ok(const void *p) { extern "C" u32 videoGetFrameCount(void); /* port/src/video.c (D75 probe) */ +/* D75 DL-sequence dump (M-196, GE_D75D="lo-hi"): the M-195 slot-overwrite + * hypothesis says every G_VTX batch encodes dest_index=0, so a multi-batch + * node's later batches clobber earlier ones and only the last survives. That + * is ONLY harmful if a node's triangles are drawn AFTER a later node's G_VTX + * has overwritten their slots -- i.e. only if the DL is structured batched + * (all vertices for all nodes, then all triangles) rather than streamed + * (each node: its vertices then its triangles). gfx_sp_tri1 copies vertex data + * into buf_vbo eagerly at triangle time, so streaming order makes cross-node + * overwrites harmless exactly like the N64 RDP. This probe records, per frame, + * a compact V/T token stream (V=G_VTX batch, T=triangle) plus max-triangle- + *index vs cumulative-vertices-loaded, to decide which structure GE's front-end + * model DLs actually use. Zero cost unless GE_D75D is set. Remove once D75 is + * root-caused. */ +static int d75d_lo = -1, d75d_hi = 0x7FFFFFFF; +static int d75d_init_done = 0; +static uint32_t d75d_frame = 0xFFFFFFFFu; +static char d75d_seq[4096]; +static int d75d_seqlen = 0; +static uint32_t d75d_maxtri = 0, d75d_vtxloaded = 0, d75d_batchhi = 0, d75d_nv = 0, d75d_nt = 0; +static uint32_t d75d_rej_triv = 0, d75d_rej_cull = 0, d75d_emitted = 0; +static uint32_t d75d_emit_on = 0, d75d_emit_off = 0; // emitted tris: NDC bbox intersects [-1,1]^2 vs not +static float d75d_ndc_minx=1e9f,d75d_ndc_maxx=-1e9f,d75d_ndc_miny=1e9f,d75d_ndc_maxy=-1e9f; +static uint32_t d75d_emit_invis = 0; // emitted tris with SHADER_OPT_INVISIBLE (G_BL_0+G_BL_CLR_MEM) +static int d75d_cmin=999, d75d_cmax=-1; // min/max over all vertex color channels of emitted tris +static uint32_t d75d_oml = 0xFFFFFFFFu; static int d75d_oml_set = 0; +static float d75d_zmin=1e9f,d75d_zmax=-1e9f; static uint32_t d75d_znan=0; static int d75d_use_tex=-1; +static uint64_t d75d_comb=0; static int d75d_prim[4]={-1,-1,-1,-1}; static int d75d_env[4]={-1,-1,-1,-1}; +static int d75d_comb_utex=-1; // comb->used_textures[0] (combiner's own claim) + +static void d75d_init(void) { + if (d75d_init_done) return; + d75d_init_done = 1; + const char* e = getenv("GE_D75D"); + if (e && *e) { + d75d_lo = atoi(e); + const char* dash = strchr(e, '-'); + if (dash) d75d_hi = atoi(dash + 1); + } +} +static bool d75d_env_active(void) { d75d_init(); return d75d_lo >= 0; } +static void d75d_flush(uint32_t f) { + if (f == 0xFFFFFFFFu || d75d_seqlen == 0) return; + fprintf(stderr, + "D75D: f=%u nv=%u nt=%u maxtri=%u vtxloaded=%u batchhi=%u rej_triv=%u rej_cull=%u emitted=%u emit_on=%u emit_off=%u ndc_x=[%.3f,%.3f] ndc_y=[%.3f,%.3f] invis=%u col=[%d,%d] oml=0x%08x omset=%d z=[%.4f,%.4f] znan=%u utex=%d cutex=%d comb=0x%016llx prim=(%d,%d,%d,%d) env=(%d,%d,%d,%d) seq=%.8s%s\n", + f, d75d_nv, d75d_nt, d75d_maxtri, d75d_vtxloaded, d75d_batchhi, + d75d_rej_triv, d75d_rej_cull, d75d_emitted, d75d_emit_on, d75d_emit_off, + d75d_ndc_minx, d75d_ndc_maxx, d75d_ndc_miny, d75d_ndc_maxy, + d75d_emit_invis, d75d_cmin, d75d_cmax, (unsigned)d75d_oml, d75d_oml_set, + d75d_zmin, d75d_zmax, d75d_znan, d75d_use_tex, d75d_comb_utex, + (unsigned long long)d75d_comb, d75d_prim[0],d75d_prim[1],d75d_prim[2],d75d_prim[3], + d75d_env[0],d75d_env[1],d75d_env[2],d75d_env[3], + d75d_seq, (d75d_seqlen >= 4096) ? "...[trunc]" : ""); + d75d_seqlen = 0; d75d_maxtri = 0; d75d_vtxloaded = 0; d75d_batchhi = 0; d75d_nv = 0; d75d_nt = 0; + d75d_rej_triv = 0; d75d_rej_cull = 0; d75d_emitted = 0; d75d_emit_on = 0; d75d_emit_off = 0; + d75d_ndc_minx=1e9f;d75d_ndc_maxx=-1e9f;d75d_ndc_miny=1e9f;d75d_ndc_maxy=-1e9f; + d75d_emit_invis = 0; d75d_cmin=999; d75d_cmax=-1; d75d_oml=0xFFFFFFFFu; d75d_oml_set=0; + d75d_zmin=1e9f;d75d_zmax=-1e9f;d75d_znan=0;d75d_use_tex=-1; + d75d_comb=0; for(int i=0;i<4;i++){d75d_prim[i]=-1;d75d_env[i]=-1;} d75d_comb_utex=-1; +} +static void d75d_frame_check(uint32_t f) { + if (f != d75d_frame) { d75d_flush(f); d75d_frame = f; } +} +static void d75d_note_vtx(uint32_t f, uint32_t count, uint32_t dest) { + d75d_init(); + if (d75d_lo < 0 || f < (uint32_t)d75d_lo || f > (uint32_t)d75d_hi) return; + d75d_frame_check(f); + d75d_nv++; + d75d_vtxloaded += count; + if (dest + count > d75d_batchhi) d75d_batchhi = dest + count; + if (d75d_seqlen < 4096) d75d_seq[d75d_seqlen++] = 'V'; +} +static void d75d_note_tri(uint32_t f, uint32_t maxidx) { + d75d_init(); + if (d75d_lo < 0 || f < (uint32_t)d75d_lo || f > (uint32_t)d75d_hi) return; + d75d_frame_check(f); + d75d_nt++; + if (maxidx > d75d_maxtri) d75d_maxtri = maxidx; + if (d75d_seqlen < 4096) d75d_seq[d75d_seqlen++] = 'T'; +} +static void d75d_note_rej(uint32_t f, int which) { + d75d_init(); + if (d75d_lo < 0 || f < (uint32_t)d75d_lo || f > (uint32_t)d75d_hi) return; + d75d_frame_check(f); + if (which == 0) d75d_rej_triv++; else d75d_rej_cull++; +} +static void d75d_note_emit(uint32_t f, struct LoadedVertex* const* v_arr) { + d75d_init(); + if (d75d_lo < 0 || f < (uint32_t)d75d_lo || f > (uint32_t)d75d_hi) return; + d75d_frame_check(f); + d75d_emitted++; + // NDC bbox of this triangle (x/w, y/w); count on-screen vs off + float mnx=1e9f,mxx=-1e9f,mny=1e9f,mxy=-1e9f; + for (int i = 0; i < 3; i++) { + float w = v_arr[i]->w; + if (w == 0.f) continue; + float nx = v_arr[i]->x / w, ny = v_arr[i]->y / w; + if (nxmxx)mxx=nx; if(nymxy)mxy=ny; + } + if (mxx < -1e8f) return; // all w==0, degenerate + if (mxx >= -1.05f && mnx <= 1.05f && mxy >= -1.05f && mny <= 1.05f) d75d_emit_on++; else d75d_emit_off++; + if (mnxd75d_ndc_maxx)d75d_ndc_maxx=mxx; + if (mnyd75d_ndc_maxy)d75d_ndc_maxy=mxy; + // invisible flag + oml + vertex color range + if ((rdp.other_mode_l & (3u << 24)) == ((uint32_t)G_BL_0 << 24) && + (rdp.other_mode_l & (3u << 20)) == ((uint32_t)G_BL_CLR_MEM << 20)) d75d_emit_invis++; + if (!d75d_oml_set) { d75d_oml = rdp.other_mode_l; d75d_oml_set = 1; } + for (int i = 0; i < 3; i++) { + int cr=v_arr[i]->color.r, cg=v_arr[i]->color.g, cb=v_arr[i]->color.b; + if (crd75d_cmax)d75d_cmax=cr; + if (cgd75d_cmax)d75d_cmax=cg; + if (cbd75d_cmax)d75d_cmax=cb; + } +} +// called at emit with the texunit-0 bound flag +static void d75d_note_emit_z(uint32_t f, struct LoadedVertex* const* v_arr, int has_tex, int comb_utex) { + d75d_init(); + if (d75d_lo < 0 || f < (uint32_t)d75d_lo || f > (uint32_t)d75d_hi) return; + d75d_frame_check(f); + for (int i = 0; i < 3; i++) { + float w = v_arr[i]->w; + if (w == 0.f) { d75d_znan++; continue; } + float nz = v_arr[i]->z / w; + if (!std::isfinite(nz)) { d75d_znan++; continue; } + if (nzd75d_zmax)d75d_zmax=nz; + } + if (d75d_use_tex < 0) d75d_use_tex = has_tex; + if (d75d_comb_utex < 0) d75d_comb_utex = comb_utex; + d75d_comb = rdp.combine_mode; + d75d_prim[0]=rdp.prim_color.r; d75d_prim[1]=rdp.prim_color.g; d75d_prim[2]=rdp.prim_color.b; d75d_prim[3]=rdp.prim_color.a; + d75d_env[0]=rdp.env_color.r; d75d_env[1]=rdp.env_color.g; d75d_env[2]=rdp.env_color.b; d75d_env[3]=rdp.env_color.a; +} + static void gfx_sp_vertex(size_t n_vertices, size_t dest_index, const Vtx* vertices) { const size_t d75_di0 = dest_index; /* D75 probe: loop below mutates dest_index */ SUPPORT_CHECK(n_vertices <= MAX_VERTICES); @@ -1780,6 +1926,13 @@ static inline int gfx_lod_tile_offset(const int i) { } static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bool is_rect) { + /* D75D: only real model triangles (all indices < MAX_VERTICES); the + * fullscreen-quad helpers use indices MAX_VERTICES+0..3 and would skew maxtri. */ + if (vtx1_idx < MAX_VERTICES && vtx2_idx < MAX_VERTICES && vtx3_idx < MAX_VERTICES) { + uint32_t d75d_mx = vtx1_idx > vtx2_idx ? (vtx1_idx > vtx3_idx ? vtx1_idx : vtx3_idx) + : (vtx2_idx > vtx3_idx ? vtx2_idx : vtx3_idx); + d75d_note_tri(videoGetFrameCount(), d75d_mx); + } struct LoadedVertex* v1 = &rsp.loaded_vertices[vtx1_idx]; struct LoadedVertex* v2 = &rsp.loaded_vertices[vtx2_idx]; struct LoadedVertex* v3 = &rsp.loaded_vertices[vtx3_idx]; @@ -1807,6 +1960,7 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo bool any_behind_camera = (v1->w < 0) || (v2->w < 0) || (v3->w < 0); if (!any_behind_camera && (v1->clip_rej & v2->clip_rej & v3->clip_rej)) { // The whole triangle lies outside the visible area + d75d_note_rej(videoGetFrameCount(), 0); return; } @@ -1994,16 +2148,19 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo switch (rsp.geometry_mode & G_CULL_BOTH) { case G_CULL_FRONT: if (cross <= 0) { + d75d_note_rej(videoGetFrameCount(), 1); return; } break; case G_CULL_BACK: if (cross >= 0) { + d75d_note_rej(videoGetFrameCount(), 1); return; } break; case G_CULL_BOTH: // Why is this even an option? + d75d_note_rej(videoGetFrameCount(), 1); return; } } @@ -2384,6 +2541,8 @@ static void gfx_sp_tri1(uint8_t vtx1_idx, uint8_t vtx2_idx, uint8_t vtx3_idx, bo struct GfxClipParameters clip_parameters = gfx_rapi->get_clip_parameters(); + d75d_note_emit(videoGetFrameCount(), v_arr); // survived all rejection gates -> reaches GL + d75d_note_emit_z(videoGetFrameCount(), v_arr, (used_textures[0] || used_textures[1]) ? 1 : 0, comb->used_textures[0] ? 1 : 0); for (int i = 0; i < 3; i++) { float z = v_arr[i]->z, w = v_arr[i]->w; if (clip_parameters.z_is_from_0_to_1) { @@ -3451,6 +3610,7 @@ static void gfx_run_dl(Gfx* cmd) { * review alone. Remove once D236 pass 16 concludes. */ g_d236_last_vtx_seg = (uint8_t)(cmd->words.w1 >> 24); gfx_sp_vertex(C0(0, 16) / sizeof(Vtx), C0(16, 4), (const Vtx*)seg_addr(cmd->words.w1)); + d75d_note_vtx(videoGetFrameCount(), C0(0, 16) / sizeof(Vtx), C0(16, 4)); break; case G_DL: { if (C0(16, 1) == 0) {