diff --git a/docs/FRAME_GENERATION.md b/docs/FRAME_GENERATION.md index 4ab4c081..054fe14d 100644 --- a/docs/FRAME_GENERATION.md +++ b/docs/FRAME_GENERATION.md @@ -33,7 +33,11 @@ has measured time to spare. - **Sources.** While generation is on, every RTPS/RTPT output is noted (gte.cpp, `gte_fg_source_*`) with an identity (the issuing function `ra` and the model-space vertex), the camera-space position the GTE divided and - H, indexed by the packed screen word. gpu.c looks each polygon vertex up by + H, indexed by the packed screen word. The same point projected again in a + frame (a corner shared by separate meshes, another function) is one vertex + (the smaller identity); up to two different points on one pixel are kept, + and gpu.c picks the one projected by the same function as the triangle's + other vertices, else the one nearest their depth. gpu.c looks each polygon vertex up by its packet word and records the sources ahead of the triangle (`RTH_FG_SRC`, never drawn). CPU-built vertices have none. Guest-visible GTE results are untouched. @@ -98,7 +102,7 @@ has measured time to spare. redrawn frame's. Areas uncovered by the in-between camera show the newer frame. Vertices without a projection that are neither on a placed edge nor shared take their triangle's mean motion (`guessed` counts those over a - 1 px spread); near R4's tunnel walls this still opens thin cracks. + 1 px spread; 0 in R4 races once shared corners resolve). - A texture the frame drew earlier into its own displayed buffer is sampled as the raw mirror holds it when the in-between frame is drawn. - Single-buffered games never flip, so nothing is generated. diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index cf75c591..1a9e2466 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -224,7 +224,8 @@ extern int gte_geometry_correction_enabled(void); /* Frame generation's vertex sources (gte.cpp): 0 = unknown. */ extern void gte_fg_source_set(int enabled); extern int gte_fg_source_enabled(void); -extern int gte_fg_source_lookup(uint32_t packed, uint32_t *id, int32_t p[3], int32_t *hdist); +typedef struct GteFgSrc { uint32_t id, ra; int32_t p[3], h; } GteFgSrc; +extern int gte_fg_source_lookup(uint32_t packed, GteFgSrc out[2]); /* Corrected vertices consumed since the last gte_geometry_correction_set — * the "is this actually doing anything on this title" counter. */ extern uint32_t gte_geometry_correction_hits(void); diff --git a/runtime/src/frame_gen.c b/runtime/src/frame_gen.c index 70a7ec55..79e080d8 100644 --- a/runtime/src/frame_gen.c +++ b/runtime/src/frame_gen.c @@ -604,12 +604,7 @@ void fg_cam_place(const FgPrimList *newer, FgCamFit *fit, const FgVert *verts, free(done); fit->clamped = clamped; fit->guessed = guessed; - /* Motion to 1/8 px: the fit's rounding error must not move an edge off - * a pixel boundary (a whole-pixel move stays whole). */ - for (uint32_t i = 0; i < n * 3u; i++) { - x[i] += roundf(dxs[i] * 8.0f) / 8.0f; - y[i] += roundf(dys[i] * 8.0f) / 8.0f; - } + for (uint32_t i = 0; i < n * 3u; i++) { x[i] += dxs[i]; y[i] += dys[i]; } /* Margins: vertices on or past a view edge that moved inward. */ if (margin) for (uint32_t j = 0; j < n; j++) diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 38f2c0cf..bee72a73 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -4478,8 +4478,29 @@ static void prepare_precise_triangle(int i0, int i1, int i2, uint32_t id[3] = { 0, 0, 0 }; int32_t pc[9] = { 0 }, hd[3] = { 0, 0, 0 }; const int ix[3] = { i0, i1, i2 }; - for (int k = 0; k < 3; k++) - if (!gte_fg_source_lookup(gp0_cmd_buf[ix[k]], &id[k], &pc[3 * k], &hd[k])) id[k] = 0; + GteFgSrc c[3][2]; + int nc[3]; + for (int k = 0; k < 3; k++) nc[k] = gte_fg_source_lookup(gp0_cmd_buf[ix[k]], c[k]); + /* Two points on one pixel: the one projected by the same function as + * a vertex with one source, else the one nearest its depth. */ + for (int k = 0; k < 3; k++) { + int pick = nc[k] == 1 ? 0 : -1; + if (nc[k] == 2) { + for (int m = 0; m < 3 && pick < 0; m++) + if (m != k && nc[m] == 1) + for (int q = 0; q < 2; q++) + if (c[k][q].ra == c[m][0].ra) { pick = q; break; } + if (pick < 0) + for (int m = 0; m < 3 && pick < 0; m++) + if (m != k && nc[m] == 1) { + const int32_t z = c[m][0].p[2]; + pick = abs(c[k][0].p[2] - z) <= abs(c[k][1].p[2] - z) ? 0 : 1; + } + } + if (pick < 0) continue; + id[k] = c[k][pick].id; hd[k] = c[k][pick].h; + for (int q = 0; q < 3; q++) pc[3 * k + q] = c[k][pick].p[q]; + } gl_renderer_fg_source(id, pc, hd, vx, vy); } gr_set_perspective_triangle(0, 0.0f, 0.0f, 0.0f); diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index 0f8f099a..6279b3a4 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -415,8 +415,8 @@ extern "C" int gte_geometry_correction_lookup_probe(uint32_t packed, * to in one frame is ambiguous. Entries a few frames old are not believed. * Allocated only while frame generation is on: the guest-visible GTE results * are untouched either way. */ -struct FgSrcEntry { uint32_t seq; uint32_t gen; }; /* gen: frame << 1 | ambiguous */ -struct FgSrcRec { uint32_t seq, id; int32_t p[3]; int32_t h; }; +struct FgSrcEntry { uint32_t seq, seq2; uint32_t gen; }; /* gen: frame << 1 | ambiguous; seq2: a second point */ +struct FgSrcRec { uint32_t seq, id, ra; int32_t p[3]; int32_t h; }; #define FGSRC_RING (1u << 17) static FgSrcEntry *s_fgsrc = nullptr; static FgSrcRec *s_fgsrc_ring = nullptr; @@ -453,6 +453,7 @@ static void fgsrc_note(const GTEState *g, int32_t packed, uint32_t ra, const int FgSrcRec &r = s_fgsrc_ring[seq & (FGSRC_RING - 1u)]; r.seq = seq; r.id = h; + r.ra = ra; for (int i = 0; i < 3; i++) { int64_t m = (int64_t)g->TR[i] * 4096; for (int k = 0; k < 3; k++) m += (int64_t)g->RT[i][k] * v[k]; @@ -461,13 +462,22 @@ static void fgsrc_note(const GTEState *g, int32_t packed, uint32_t ra, const int r.h = gte_h_scaled(g); FgSrcEntry &e = s_fgsrc[slot]; const uint32_t f = fgsrc_frame(); - if ((e.gen >> 1) == f && e.seq && s_fgsrc_ring[e.seq & (FGSRC_RING - 1u)].seq == e.seq && - s_fgsrc_ring[e.seq & (FGSRC_RING - 1u)].id != h) { - e.gen |= 1u; /* two vertices, one pixel */ - return; - } - if ((e.gen >> 1) != f) e.gen = f << 1; - e.seq = seq; + if ((e.gen >> 1) != f) { e.gen = f << 1; e.seq = seq; e.seq2 = 0; return; } + /* Earlier this frame: the same point projected again (another function, + * another model vertex: shared corners of separate meshes) is one vertex, + * keeping the smaller identity so the choice does not depend on draw + * order. Up to two different points on one pixel are kept (the GPU side + * picks by the triangle's other vertices); a third is ambiguous. */ + auto same = [&](uint32_t sq) { + const FgSrcRec &o = s_fgsrc_ring[sq & (FGSRC_RING - 1u)]; + return o.seq == sq && std::abs(o.p[0] - r.p[0]) <= 2 && std::abs(o.p[1] - r.p[1]) <= 2 && + std::abs(o.p[2] - r.p[2]) <= 2; + }; + auto keep = [&](uint32_t &sq) { if (s_fgsrc_ring[sq & (FGSRC_RING - 1u)].id > h) sq = seq; }; + if (same(e.seq)) { keep(e.seq); return; } + if (!e.seq2) { e.seq2 = seq; return; } + if (same(e.seq2)) { keep(e.seq2); return; } + e.gen |= 1u; } static void fgsrc_record(const GTEState *g, uint32_t cmd, uint32_t ra) { @@ -481,22 +491,27 @@ static void fgsrc_record(const GTEState *g, uint32_t cmd, uint32_t ra) { } } -/* The source of the vertex last projected to a packed screen word: 0 when - * none, ambiguous or stale; else 1 with its identity, camera-space position - * and projection distance. */ -extern "C" int gte_fg_source_lookup(uint32_t packed, uint32_t *id, int32_t p[3], int32_t *hdist) { +/* The sources of the vertices last projected to a packed screen word this + * frame (or a few frames ago): 0 when none, ambiguous or stale, else 1 or 2 + * (two different points on that pixel). */ +extern "C" int gte_fg_source_lookup(uint32_t packed, GteFgSrc out[2]) { if (!s_fgsrc) return 0; const int64_t slot = geom_slot(packed); if (slot < 0) return 0; const FgSrcEntry &e = s_fgsrc[slot]; if (!e.seq || (e.gen & 1u)) return 0; if (((fgsrc_frame() - (e.gen >> 1)) & 0x7FFFFFFFu) > FGSRC_MAX_AGE) return 0; - const FgSrcRec &r = s_fgsrc_ring[e.seq & (FGSRC_RING - 1u)]; - if (r.seq != e.seq) return 0; - *id = r.id; - p[0] = r.p[0]; p[1] = r.p[1]; p[2] = r.p[2]; - *hdist = r.h; - return 1; + int n = 0; + const uint32_t sq[2] = { e.seq, e.seq2 }; + for (int i = 0; i < 2; i++) { + if (!sq[i]) continue; + const FgSrcRec &r = s_fgsrc_ring[sq[i] & (FGSRC_RING - 1u)]; + if (r.seq != sq[i]) continue; + out[n].id = r.id; out[n].ra = r.ra; out[n].h = r.h; + for (int k = 0; k < 3; k++) out[n].p[k] = r.p[k]; + n++; + } + return n; } /* Render-pass checkpoint for the gte.cpp side of precision tracking: the