mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Frame generation: shared corners are one vertex; no motion rounding
The remaining tunnel cracks came from vertex sources, not from missing depth: 46 % of R4 lookups were marked ambiguous because the same 3D point is projected several times per frame (corners shared by separate meshes, other functions), so those vertices fell back to a triangle-mean guess. - The same point projected again is one vertex (the smaller identity, so the choice does not follow draw order). - Up to two different points on one pixel are kept; gpu.c picks the one projected by the same function as the triangle's other vertices, else the one nearest their depth. - Per-vertex motion is no longer rounded to 1/8 px: at high internal scales the rounding opened hairlines along long T-junction seams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++)
|
||||
|
||||
+23
-2
@@ -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);
|
||||
|
||||
+34
-19
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user