mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
D954: at 90+ fps, props (guards, doors, objects, effects) follow the blended camera
In-between presents drew props at the tick's camera while rooms used the blended one, so props jumped against the world whenever the camera moved (doors and guards flickering at 90 fps, never at 60). The props' plain perspective now gets the room camera blend (P * R^-1 * Rb) from bgLevelRender up to the first-person gun. Port layer only (gfx_pc.cpp, interpportal.c).
This commit is contained in:
1 parent
7d3abbf813
commit
b7280e5386
8 files changed
+269
-1
No files matched your search
@@ -200,6 +200,7 @@ A single campaign pass with these on the checklist would clear most of them.
|
||||
| Steam Deck preset skipped when first launched from Desktop Mode | verify | D283 | Fixed on `release/v0.4.1`: the Deck is detected by DMI hardware id (Valve Jupiter/Galileo; `STEAMOS` as a second signal) and the preset applies once per ini, only while Fullscreen/Maximized are untouched. Only a Windows build and a Linux `-fsyntax-only` check ran. **Owed: one run on a real Deck** (Desktop-Mode-created ini, then Game Mode; expect fullscreen 1280x800 and the log line `video: Steam Deck (DMI Valve ...)`). The Known-issues row goes when this ships. |
|
||||
| Windows 7 | open | #98 | **Re-checked 2026-10-02: likely feasible around 1.0.** The exe imports no Windows 8+ functions and the bundled SDL2 2.32.10 still supports Windows 7. The real blocker is the first-run converter `ge007-convert.exe` (PyInstaller, Python 3.9+): the native C converter (#6 Part B) removes it, or a separate Python 3.8 build. Work: one Windows 7 VM test, then manual per-release checks (CI cannot test it). Supersedes the earlier 'won't-fix' call. |
|
||||
| Alt-tab haze + fast alt-tab recovery | open (v0.7.0, maintainer 2026-10-10) | D578 | Maintainer 2026-10-10 (v0.6.0 build, Windows 120 Hz): alt-tabbing leaves a hazy picture; fast repeated alt-tab is wonky and recovers only after a while or more alt-tabbing. Not new in v0.6.0 (was an owed check). Known issue in the v0.6.0 notes. QoL fix: clean focus-loss/regain path (context/FBO state, present queue reset). |
|
||||
| Doors and guards flicker at 90 fps (not at 60) | fixed 2026-10-13 (D954); maintainer Deck check owed | D954 | In-between presents drew props (chrs, doors, effects) at the exact camera while rooms used the blended one, so props jumped against the world whenever the camera moved (Surface turn on Windows: 0.4-0.5 deg mean, up to 1.8 deg). Present in v0.6.0 at 90 fps. Fix in `gfx_pc.cpp`: props' plain perspective gets the room camera blend (`P * R^-1 * Rb`) up to the first-person gun. **Owed:** Deck at 90 fps, Surface doors/guards, and the gun must not move while turning. |
|
||||
| Windows display scaling: the game is not DPI-aware on scaled displays | fixed 2026-10-10 (D626); maintainer by-eye on a scaled display owed | D626 | Seen 2026-10-07 during the v0.5.0 4K captures: on a 3840x2160 display at 125% Windows gives the game a 3072x1728 surface and stretches it, so the picture is softer than native. `gfx_sdl2.cpp` sets `SDL_HINT_WINDOWS_DPI_AWARENESS` only when `allow_hidpi` is off; setting it (`permonitorv2`) on the HiDPI path too renders at native resolution. Workaround today: environment variable `SDL_WINDOWS_DPI_AWARENESS=permonitorv2`. |
|
||||
|
||||
## 5. Modern-remaster layer (port features beyond the N64)
|
||||
|
||||
@@ -28,6 +28,7 @@ Two classes:
|
||||
| `GE_INTERPPORTAL_OFF` | `port/fast3d/gfx_pc.cpp:4156` | D578: disable the portal-scissor replay on in-between passes. | live switch |
|
||||
| `GE_INTERPROOM_OFF` | `port/fast3d/gfx_pc.cpp:6059` | D578: disable the room-change exact fallback. | live switch |
|
||||
| `GE_INTERPREACH_OFF` | `port/fast3d/gfx_pc.cpp:6128` | D583: disable the REACH exact fallback. | live switch |
|
||||
| `GE_INTERPPROPCAM_OFF` | `port/fast3d/gfx_pc.cpp:2436` | D954: in-between presents draw props at the exact camera again (no room-camera blend on the plain perspective). | live switch |
|
||||
| `GE_PCDUMP_PRESENTS` | `port/src/video.c:1539` | With `GE_PCDUMP`: one capture per present instead of per game frame. | live tooling |
|
||||
| `GE_NOTICKLOCK` | `port/src/libultra.c:1321` | D600: disable the display-locked retrace tick. | live switch |
|
||||
| `GE_ADDRSTRICT_SELFTEST` | `port/src/port_addr.c:122` | D607 (macOS): address-window self-test at startup. | live diag |
|
||||
|
||||
@@ -464,6 +464,7 @@ D645,FIXED,"Original 1.1: F10 ""Invert look"" did not invert vertical look (#145
|
||||
D646,FIXED,"F10 dropdown popup drew over the hint/tooltip panel (popup bounded by the screen edge, not the settings box) —",FIXED 2026-10-12 (ddGeom bounded to the box; maintainer-verified)
|
||||
D951,OPEN,"v0.6.0 release gate: Windows golden sweep flags are timing noise in the test, not rendering changes —",OPEN (test-side; 2026-10-13)
|
||||
D952,FIXED,"macOS: black window, no frame, when the game starts while the display is asleep (SDL3 Cocoa VSync waits forever on a CVDisplayLink that never started) —","FIXED 2026-10-13 (port-only, gfx_sdl2.cpp; Mac A/B:"
|
||||
D954,FIXED,"Doors and guards flicker at 90 fps, not at 60:","FIXED 2026-10-13 (port-only, gfx_pc.cpp + interpportal.c; Windows-measured; Deck check owed)"
|
||||
D607,OPEN,Address model unified into one header:,DONE 2026-10-07 (macos branch; Windows identity gate:
|
||||
D608,FIXED,D34 ANIM_DATA_x re-basing guard in assets/animationtable_data.h was __x86_64__-only:,"FIXED 2026-10-07 (macos branch; mechanical guard widening, arm64 link clean)"
|
||||
D609,FIXED,macOS arm64 SIGKILL at exec:,"FIXED 2026-10-07 (macos branch; probe-confirmed; arm64 keeps the 4 GiB pagezero, floor 11.0)"
|
||||
|
||||
|
@@ -875,6 +875,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
|
||||
| D646 | **F10 dropdown popup drew over the hint/tooltip panel (popup bounded by the screen edge, not the settings box)** — full `## D646` entry at file tail | FIXED 2026-10-12 (`ddGeom` bounded to the box; maintainer-verified) |
|
||||
| D951 | **v0.6.0 release gate: Windows golden sweep flags are timing noise in the test, not rendering changes** — full `## D951` entry at file tail | OPEN (test-side; 2026-10-13) |
|
||||
| D952 | **macOS: black window, no frame, when the game starts while the display is asleep (SDL3 Cocoa VSync waits forever on a CVDisplayLink that never started)** — full `## D952` entry at file tail | FIXED 2026-10-13 (port-only, `gfx_sdl2.cpp`; Mac A/B: unfixed 3/3 hang, fixed 0/5) |
|
||||
| D954 | **Doors and guards flicker at 90 fps, not at 60: in-between presents drew props at the exact camera while rooms used the blended one** — full `## D954` entry at file tail | FIXED 2026-10-13 (port-only, `gfx_pc.cpp` + `interpportal.c`; Windows-measured; Deck check owed) |
|
||||
|
||||
Phase 2 replaced the Phase-1 demo loop with the real `mainproc()` on real OS
|
||||
threads, compiled GE's real `src/sched.c`, and brought in PD's fast3d software
|
||||
@@ -22014,3 +22015,17 @@ FpsCap = 60'`: 20/21 PASS; statue REGRESSION (4.66%, frame 900).
|
||||
**Verified (Mac M3, macOS 15.6.1, fix built with the CI recipe, run against the download's own SDL 3.4.14 dylibs).** Display asleep: fixed 0/5 hang. Display awake: 60.0 presents/s as before; VSync on/off read back 1/0 from CGL. 21-level sweep (mac_sweep.sh, fixed binary): 21/21 OK. Windows build clean; Windows/Linux code paths unchanged (`#else` = the old lines).
|
||||
|
||||
**Status.** FIXED.
|
||||
|
||||
## D954: doors and guards flicker at 90 fps (not at 60) — in-between presents drew props at the exact camera, rooms at the blended one — FIXED 2026-10-13 (port layer; maintainer Deck check owed)
|
||||
|
||||
**Symptom (maintainer, Deck, Surface).** Doors and guards flicker at 90 fps; never at 60 fps. First seen on a D953 test build, but nothing in D953 depends on frame rate (LOD is chosen once per game tick and the in-between passes replay the same list), so the trigger is the D578 interpolation, which only runs above 60 fps.
|
||||
|
||||
**Cause (code).** GE draws rooms under `field_10E0` (perspective * lookat, `bondview2.c`; room modelviews are scale*translation) and everything else in the world — chrs, doors and other props (`bg.c` `chrpropsRenderPass`), glass, explosions, casings — under the plain perspective `projmatrix`, with the camera in the modelview (`model.c` uses `-mtx->m[3][2]` as view depth). Since the D578 modelview-pairing fix (Deck A/B 2026-10-09) in-between passes blend only the projection and draw modelviews exactly as loaded. So on an in-between present the rooms are drawn at the blended camera and every prop at camera N: whenever the camera moves, props jump against the world, then snap back on the exact present. The older `s_interp_corr` camera correction for modelviews became unreachable with that change (it sits after the modelview early return). Not caused by D953 (which only moves LOD switch distances, identically at 60 and 90 fps) and present in v0.6.0 at 90 fps.
|
||||
|
||||
**Measured (Windows, Surface 1, 1280x800, FpsCap 90, `GE_INTERP_FORCE=1`, scripted look left/right).** Per 10 s while turning: 634-637 in-between passes with the props off the room camera, mean 0.39-0.52 deg, max 1.53-1.84 deg (about 5 px mean and up to 22 px at 800 px tall and 60 deg vertical FOV). Not visible at 60 fps (no in-between passes).
|
||||
|
||||
**Fix (`port/fast3d/gfx_pc.cpp`, `port/src/interpportal.c`; no game code).** In the world part of a blend pass, a projection LOAD equal to the frame's plain perspective P is replaced by `P * R^-1 * Rb` (R = the frame's room projection, Rb = its blended version from the same `gfx_interp_matrix` pairing and memo the room loads use). Row vectors, `R = L * P`, so `P * R^-1 = L^-1` and a prop vertex lands exactly where a room vertex at the same world point lands. P and R are copied on the game thread with the frame's task (`interpPortalOnSend` snapshot, `cam_ok` only for one player). Window: armed at `gSPSegment(SPSEGMENT_BG_DL)` (`bgLevelRender`: after the sky, before the first room's props), disarmed at the next `gSPPerspNormalize`, which is the first-person gun (`gunfire.c`): the gun inherits the loaded projection and must stay at the exact camera, so the uncorrected P is restored there. Exact passes, split-screen, menus and 60 fps are untouched. `GE_INTERPPROPCAM_OFF=1` disables it. Runtime check, logged once: `R * P^-1` must be affine (measured last column -1.4e-06 0 1.1e-06 1: ok); a failure disables the fix.
|
||||
|
||||
**Verified.** Windows build; Surface run as above: convention check ok, 0 passes without a snapshot, the gun disarm hit 431 times per 10 s of gameplay (the remaining passes had no gun drawn). New 10 s log line `D954 prop camera: ...`. Deck build (release recipe: RelWithDebInfo, gcc-14, rpath `$ORIGIN`, bundled SDL2 2.30.0) deployed to the maintainer's shortcut. **Owed:** maintainer Deck check at 90 fps (Surface doors and guards; also the first-person gun while turning, which must not move).
|
||||
|
||||
**Status.** FIXED (Deck confirmation owed).
|
||||
@@ -2012,3 +2012,7 @@ A 100 % score on a derived metric is a circularity smell, not a result.
|
||||
## D44. RDP IA4 is I3:A1; a decode tweak justified by "hardware layout" needs a texel dump (D644)
|
||||
|
||||
IA4 = 3-bit intensity + 1-bit alpha (nibble = I<<1 | A). An I2:A2 decode (D266) turned the A=0 nibbles 2/4/6 into semi-opaque black and drew the file-select crest on a dark square. When a texture's transparent texels render opaque, histogram the raw nibbles/bytes (`GE_TEXRAW` + `GE_D526` for the draw's fmt/oml/comb) before touching blender or alpha-compare code: the importer's alpha expansion is the cheapest suspect.
|
||||
|
||||
## D45. Interpolating GE: rooms carry the camera in the projection, props carry it in the modelview (D954)
|
||||
|
||||
GE loads two projections per view: `field_10E0` = perspective * lookat for rooms (modelviews are scale*translation), and the plain perspective `projmatrix` for chrs, doors, props, glass, explosions and the gun (camera baked into each modelview; LOD reads `-m[3][2]` as view depth). An interpolator that blends only the projection moves the rooms and leaves every prop at the exact camera, which reads as props flickering against the world at high frame rates only. Give the plain perspective the room camera blend (`P * R^-1 * Rb`, row vectors, `R = L * P`), and stop before anything camera-attached (the gun inherits the loaded projection; its `gSPPerspNormalize` marks the boundary). Check the matrix order at runtime (`R * P^-1` affine) instead of assuming it.
|
||||
@@ -2396,6 +2396,174 @@ static void gfx_interp_matrix_body(float matrix[4][4], bool proj, bool load) {
|
||||
}
|
||||
}
|
||||
|
||||
/* D954: props follow the in-between camera. GE draws rooms under field_10E0
|
||||
* (perspective * lookat, modelviews scale*translation) and everything else in
|
||||
* the world -- chrs, doors and other props, glass, explosions, casings --
|
||||
* under the plain perspective projmatrix with the camera in the modelview
|
||||
* (bg.c chrpropsRenderPass; model.c LOD distance = view-space depth).
|
||||
* In-between passes blend only the projection and draw modelviews exactly
|
||||
* (D578, Deck A/B 2026-10-09), so rooms moved with the blended camera while
|
||||
* props stayed at camera N: on every in-between present, whenever the camera
|
||||
* moved, props jumped against the world and snapped back on the exact present
|
||||
* (doors and guards flickering at 90 fps, never at 60).
|
||||
*
|
||||
* Fix: in the world part of a blend pass, a projection LOAD equal to the
|
||||
* frame's plain perspective P is replaced by P * R^-1 * Rb, R = the frame's
|
||||
* room projection, Rb = its blended version. Row vectors, R = L * P (checked
|
||||
* once at runtime: R * P^-1 must be affine), so P * R^-1 = L^-1 and a prop
|
||||
* drawn v * (W * L) * (L^-1 * Rb) = v * W * Rb lands exactly where a room
|
||||
* vertex at the same world point lands. R and P come from the game-thread
|
||||
* snapshot (interpPortalCamMatrices); Rb is the same pairing/blend the room
|
||||
* loads get (gfx_interp_matrix, whose per-pass memo hands the room loads the
|
||||
* identical result). Window: armed at gSPSegment(SPSEGMENT_BG_DL)
|
||||
* (bgLevelRender: after the sky, before any room or prop), disarmed at the
|
||||
* next gSPPerspNormalize (the first-person gun, gunfire.c, which inherits the
|
||||
* loaded projection and must stay at the exact camera) and at every pass
|
||||
* start. Exact passes, split-screen and menus are untouched; nothing changes
|
||||
* at 60 fps (no blend passes). GE_INTERPPROPCAM_OFF=1 disables it. */
|
||||
static bool s_pcam_armed = false;
|
||||
static bool s_pcam_applied = false; /* rsp.P_matrix currently holds a corrected P */
|
||||
static float s_pcam_P[4][4]; /* frame's plain perspective, decoded */
|
||||
static float s_pcam_fix[4][4]; /* P * R^-1 * Rb */
|
||||
static float s_pcam_unfixed[4][4]; /* what the last corrected load would have been */
|
||||
static int s_pcam_conv = 0; /* 0 unchecked, 1 ok, -1 failed (disabled) */
|
||||
static unsigned s_pcam_cnt_pass = 0, s_pcam_cnt_load = 0, s_pcam_cnt_nosnap = 0, s_pcam_cnt_gun = 0;
|
||||
static float s_pcam_deg_sum = 0.0f, s_pcam_deg_max = 0.0f;
|
||||
|
||||
static bool interp_propcam_off(void) {
|
||||
static int off = -1;
|
||||
if (off < 0) {
|
||||
off = getenv("GE_INTERPPROPCAM_OFF") != nullptr ? 1 : 0;
|
||||
}
|
||||
return off == 1;
|
||||
}
|
||||
|
||||
static void gfx_decode_mtx_words(const int32_t* addr, float m[4][4]) {
|
||||
#ifndef GBI_FLOATS
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j += 2) {
|
||||
int32_t int_part = addr[i * 2 + j / 2];
|
||||
uint32_t frac_part = addr[8 + i * 2 + j / 2];
|
||||
m[i][j] = (int32_t)((int_part & 0xffff0000) | (frac_part >> 16)) / 65536.0f;
|
||||
m[i][j + 1] = (int32_t)((int_part << 16) | (frac_part & 0xffff)) / 65536.0f;
|
||||
}
|
||||
}
|
||||
#else
|
||||
memcpy(m, addr, sizeof(float) * 16);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* General 4x4 inverse (Gauss-Jordan, partial pivoting). */
|
||||
static bool gfx_mat4_inverse(const float in[4][4], float out[4][4]) {
|
||||
double a[4][8];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
a[i][j] = in[i][j];
|
||||
a[i][j + 4] = (i == j) ? 1.0 : 0.0;
|
||||
}
|
||||
}
|
||||
for (int c = 0; c < 4; c++) {
|
||||
int piv = c;
|
||||
for (int r = c + 1; r < 4; r++) {
|
||||
if (fabs(a[r][c]) > fabs(a[piv][c])) {
|
||||
piv = r;
|
||||
}
|
||||
}
|
||||
if (fabs(a[piv][c]) < 1e-12) {
|
||||
return false;
|
||||
}
|
||||
if (piv != c) {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
const double t = a[c][j];
|
||||
a[c][j] = a[piv][j];
|
||||
a[piv][j] = t;
|
||||
}
|
||||
}
|
||||
const double d = a[c][c];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
a[c][j] /= d;
|
||||
}
|
||||
for (int r = 0; r < 4; r++) {
|
||||
if (r != c && a[r][c] != 0.0) {
|
||||
const double f = a[r][c];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
a[r][j] -= f * a[c][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
out[i][j] = (float)a[i][j + 4];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* gSPSegment(SPSEGMENT_BG_DL): the world part of the pass begins. */
|
||||
static void gfx_pcam_arm(void) {
|
||||
s_pcam_armed = false;
|
||||
if (s_interp_mode != INTERP_BLEND || s_interp_alpha >= 1.0f || s_pcam_conv < 0 || interp_propcam_off()) {
|
||||
return;
|
||||
}
|
||||
int32_t pw[16], rw[16];
|
||||
if (!interpPortalCamMatrices(pw, rw)) {
|
||||
s_pcam_cnt_nosnap++;
|
||||
return;
|
||||
}
|
||||
float P[4][4], R[4][4], Rinv[4][4];
|
||||
gfx_decode_mtx_words(pw, P);
|
||||
gfx_decode_mtx_words(rw, R);
|
||||
if (!gfx_mat4_inverse(R, Rinv)) {
|
||||
return;
|
||||
}
|
||||
if (s_pcam_conv == 0) {
|
||||
/* R = L * P (row vectors) <=> R * P^-1 is affine (last column 0,0,0,1). */
|
||||
float Pinv[4][4], L[4][4];
|
||||
bool ok = gfx_mat4_inverse(P, Pinv);
|
||||
if (ok) {
|
||||
gfx_matrix_mul(L, R, Pinv);
|
||||
const float sc = fmaxf(fabsf(L[0][0]) + fabsf(L[0][1]) + fabsf(L[0][2]), 1e-6f);
|
||||
ok = fabsf(L[0][3]) < 1e-3f * sc && fabsf(L[1][3]) < 1e-3f * sc && fabsf(L[2][3]) < 1e-3f * sc &&
|
||||
fabsf(L[3][3] - 1.0f) < 1e-2f;
|
||||
sysLogPrintf(LOG_NOTE, "D954 prop camera: convention check %s (R*P^-1 last column %.4g %.4g %.4g %.4g)",
|
||||
ok ? "ok" : "FAILED -> disabled", L[0][3], L[1][3], L[2][3], L[3][3]);
|
||||
}
|
||||
s_pcam_conv = ok ? 1 : -1;
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* The room projection's in-between version, exactly as its loads get it. */
|
||||
float Rb[4][4];
|
||||
memcpy(Rb, R, sizeof(Rb));
|
||||
gfx_interp_matrix(Rb, true, true);
|
||||
if (s_interp_alpha >= 1.0f || !memcmp(Rb, R, sizeof(Rb))) {
|
||||
return; /* exact pass (or no partner): nothing to correct */
|
||||
}
|
||||
float tmp[4][4];
|
||||
gfx_matrix_mul(tmp, P, Rinv);
|
||||
gfx_matrix_mul(s_pcam_fix, tmp, Rb);
|
||||
memcpy(s_pcam_P, P, sizeof(s_pcam_P));
|
||||
s_pcam_armed = true;
|
||||
s_pcam_cnt_pass++;
|
||||
const float deg = gfx_interp_cam_offset_deg(R, Rb);
|
||||
s_pcam_deg_sum += deg;
|
||||
if (deg > s_pcam_deg_max) {
|
||||
s_pcam_deg_max = deg;
|
||||
}
|
||||
}
|
||||
|
||||
/* gSPPerspNormalize (the gun): leave the world part, back to the exact P. */
|
||||
static void gfx_pcam_disarm(void) {
|
||||
if (s_pcam_applied) {
|
||||
memcpy(rsp.P_matrix, s_pcam_unfixed, sizeof(s_pcam_unfixed));
|
||||
gfx_matrix_mul(rsp.MP_matrix, rsp.modelview_matrix_stack[rsp.modelview_matrix_stack_size - 1], rsp.P_matrix);
|
||||
s_pcam_applied = false;
|
||||
}
|
||||
s_pcam_armed = false;
|
||||
}
|
||||
|
||||
static void gfx_sp_matrix(uint8_t parameters, const int32_t* addr) {
|
||||
float matrix[4][4];
|
||||
|
||||
@@ -2429,11 +2597,22 @@ static void gfx_sp_matrix(uint8_t parameters, const int32_t* addr) {
|
||||
memcpy(matrix, addr, sizeof(matrix));
|
||||
#endif
|
||||
|
||||
/* D954: a plain-perspective load inside the world part of a blend pass. */
|
||||
const bool pcam = s_pcam_armed && !addr_bad && (parameters & G_MTX_PROJECTION) && (parameters & G_MTX_LOAD) &&
|
||||
!memcmp(matrix, s_pcam_P, sizeof(matrix));
|
||||
|
||||
if (s_interp_mode != INTERP_OFF && !addr_bad) { /* D578 */
|
||||
gfx_interp_matrix(matrix, (parameters & G_MTX_PROJECTION) != 0, (parameters & G_MTX_LOAD) != 0);
|
||||
}
|
||||
|
||||
if (parameters & G_MTX_PROJECTION) {
|
||||
s_pcam_applied = false;
|
||||
if (pcam) {
|
||||
memcpy(s_pcam_unfixed, matrix, sizeof(matrix));
|
||||
memcpy(matrix, s_pcam_fix, sizeof(matrix));
|
||||
s_pcam_applied = true;
|
||||
s_pcam_cnt_load++;
|
||||
}
|
||||
if (parameters & G_MTX_LOAD) {
|
||||
memcpy(rsp.P_matrix, matrix, sizeof(matrix));
|
||||
} else {
|
||||
@@ -4370,6 +4549,12 @@ static void gfx_sp_moveword(uint8_t index, uint16_t offset, uintptr_t data) {
|
||||
rsp.fog_mul = (int16_t)(data >> 16);
|
||||
rsp.fog_offset = (int16_t)data;
|
||||
break;
|
||||
case G_MW_PERSPNORM:
|
||||
if (s_pcam_armed || s_pcam_applied) {
|
||||
s_pcam_cnt_gun++;
|
||||
gfx_pcam_disarm(); /* D954: the first-person gun follows */
|
||||
}
|
||||
break;
|
||||
case G_MW_SEGMENT: {
|
||||
// GE registers segment bases as OS_K0_TO_PHYSICAL(ptr); store the
|
||||
// live host pointer so seg_addr() resolves seg+offset correctly.
|
||||
@@ -4382,6 +4567,9 @@ static void gfx_sp_moveword(uint8_t index, uint16_t offset, uintptr_t data) {
|
||||
v = (uintptr_t)portN64ToHost((u32)v);
|
||||
}
|
||||
segmentPointers[(offset >> 2) & 0xff] = v;
|
||||
if (((offset >> 2) & 0xff) == 15 && s_interp_mode == INTERP_BLEND) {
|
||||
gfx_pcam_arm(); /* D954: SPSEGMENT_BG_DL = bgLevelRender, the world part begins */
|
||||
}
|
||||
if (((offset >> 2) & 0xff) == 14) {
|
||||
s_interp_seg14 = (uint32_t)data;
|
||||
if (s_interp_mode != INTERP_OFF) {
|
||||
@@ -6020,6 +6208,8 @@ extern "C" int gfx_interp_tick(Gfx* commands, const float* alphas, int n, int ba
|
||||
s_interp_unmatched = 0;
|
||||
s_interp_clamp_req = 0.0f;
|
||||
s_interp_rooms_pass.clear();
|
||||
s_pcam_armed = false; /* D954 (rsp was restored from the snapshot above) */
|
||||
s_pcam_applied = false;
|
||||
gfx_frame_body(commands, passes == 0, &overlay, 0, &unused0, &unused1);
|
||||
s_interp_body_runs++;
|
||||
record = false;
|
||||
@@ -6226,6 +6416,11 @@ extern "C" int gfx_interp_tick(Gfx* commands, const float* alphas, int n, int ba
|
||||
sysLogPrintf(LOG_NOTE, "D583 projection memo: %u repeat loads, %u where the pairing gave a different camera",
|
||||
s_interp_pmemo_hit, s_interp_pmemo_diff);
|
||||
s_interp_pmemo_hit = s_interp_pmemo_diff = 0;
|
||||
sysLogPrintf(LOG_NOTE, "D954 prop camera: %u passes corrected, %u prop projection loads, prop-vs-room camera offset removed: mean %.2f max %.2f deg, %u passes without a snapshot, %u ended at the gun",
|
||||
s_pcam_cnt_pass, s_pcam_cnt_load, s_pcam_cnt_pass ? s_pcam_deg_sum / (float)s_pcam_cnt_pass : 0.0f,
|
||||
s_pcam_deg_max, s_pcam_cnt_nosnap, s_pcam_cnt_gun);
|
||||
s_pcam_cnt_pass = s_pcam_cnt_load = s_pcam_cnt_nosnap = s_pcam_cnt_gun = 0;
|
||||
s_pcam_deg_sum = s_pcam_deg_max = 0.0f;
|
||||
s_interp_cnt_ip_exact = s_interp_cnt_ip_soft = s_interp_cnt_late_exact = s_interp_cnt_late_soft = 0;
|
||||
if (s_interp_hid_entering > 0) {
|
||||
sysLogPrintf(LOG_NOTE, "D583 entering rooms hidden in in-between passes: %u", s_interp_hid_entering);
|
||||
@@ -6235,6 +6430,8 @@ extern "C" int gfx_interp_tick(Gfx* commands, const float* alphas, int n, int ba
|
||||
}
|
||||
s_interp_mode = INTERP_OFF;
|
||||
s_interp_record = false;
|
||||
s_pcam_armed = false; /* D954 */
|
||||
s_pcam_applied = false;
|
||||
s_interp_prev.swap(s_interp_cur);
|
||||
s_interp_have_prev = true;
|
||||
if (s_interp_rooms_prev_valid && s_interp_rooms_pass != s_interp_rooms_prev) {
|
||||
|
||||
@@ -56,6 +56,9 @@ int interpPortalTakeExtraRooms(uint32_t *vphys, int *prevdrew, int max);
|
||||
* REACH test above decides holes geometrically). */
|
||||
int interpPortalReplayedThisPass(void);
|
||||
unsigned interpPortalExtraCount(void);
|
||||
/* D954: the drawn frame's plain perspective (props) and perspective*lookat
|
||||
* (rooms) as fixed-point Mtx words; 0 = unavailable (split-screen, menus). */
|
||||
int interpPortalCamMatrices(int32_t proj[16], int32_t room[16]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+47
-1
@@ -127,11 +127,21 @@ typedef struct {
|
||||
int ndrawn;
|
||||
s32 d_room[RP_MAXDRAWN];
|
||||
bbox2d d_bbox[RP_MAXDRAWN];
|
||||
|
||||
/* D954: the two projections the frame loads, as the game built them
|
||||
* (fixed-point Mtx words, the layout gfx_sp_matrix decodes): projmatrix =
|
||||
* plain perspective (props, chrs, effects: camera in their modelviews),
|
||||
* field_10E0 = perspective * lookat (rooms). Captured even when the portal
|
||||
* replay is off; cam_ok = 0 for split-screen or a missing matrix. */
|
||||
int cam_ok;
|
||||
s32 cam_proj[16];
|
||||
s32 cam_room[16];
|
||||
} IpSnap;
|
||||
|
||||
static IpSnap s_ring[RP_SLOTS];
|
||||
static int s_ringNext;
|
||||
static const IpSnap *S; /* snapshot of the frame being drawn (render thread) */
|
||||
static const IpSnap *SC; /* D954: same frame, camera matrices (valid or not for the replay) */
|
||||
static unsigned s_snapSerial;
|
||||
|
||||
typedef struct IpSchedTask { /* mirror of OSScTask (src/sched.h) up to `list` */
|
||||
@@ -142,6 +152,23 @@ typedef struct IpSchedTask { /* mirror of OSScTask (src/sched.h) up to `l
|
||||
OSTask list;
|
||||
} IpSchedTask;
|
||||
|
||||
extern s32 getPlayerCount(void);
|
||||
|
||||
/* D954: game thread, with the frame's graphics task. */
|
||||
static void ip_snapshot_cam(IpSnap *s, const void *dl)
|
||||
{
|
||||
struct player *p = g_CurrentPlayer;
|
||||
_Static_assert(sizeof(Mtx) == sizeof(s->cam_proj), "D954: Mtx must be 16 fixed-point words");
|
||||
s->dl = dl;
|
||||
s->cam_ok = 0;
|
||||
if (p == NULL || p->projmatrix == NULL || p->field_10E0 == NULL || getPlayerCount() != 1) {
|
||||
return;
|
||||
}
|
||||
memcpy(s->cam_proj, p->projmatrix, sizeof(s->cam_proj));
|
||||
memcpy(s->cam_room, p->field_10E0, sizeof(s->cam_room));
|
||||
s->cam_ok = 1;
|
||||
}
|
||||
|
||||
static void ip_snapshot(IpSnap *s, const void *dl)
|
||||
{
|
||||
struct player *p = g_CurrentPlayer;
|
||||
@@ -214,7 +241,6 @@ void interpPortalOnSend(void *mq, void *msg)
|
||||
int i;
|
||||
|
||||
if (mq != (void *) sched_cmdQ || msg == NULL || msg != g_gfxTaskSettingsList) { return; }
|
||||
if (rp_env("GE_INTERPPORTAL_OFF", &env_off)) { return; }
|
||||
t = (IpSchedTask *) msg;
|
||||
for (i = 0; i < RP_SLOTS; i++) { /* same display list again: reuse its slot */
|
||||
if (s_ring[i].dl == (const void *) t->list.t.data_ptr) { break; }
|
||||
@@ -223,9 +249,25 @@ void interpPortalOnSend(void *mq, void *msg)
|
||||
i = s_ringNext;
|
||||
s_ringNext = (s_ringNext + 1) % RP_SLOTS;
|
||||
}
|
||||
ip_snapshot_cam(&s_ring[i], (const void *) t->list.t.data_ptr); /* D954 */
|
||||
if (rp_env("GE_INTERPPORTAL_OFF", &env_off)) {
|
||||
s_ring[i].valid = 0;
|
||||
return;
|
||||
}
|
||||
ip_snapshot(&s_ring[i], (const void *) t->list.t.data_ptr);
|
||||
}
|
||||
|
||||
/* D954: the frame's two projections (see IpSnap.cam_*); 0 when unavailable. */
|
||||
int interpPortalCamMatrices(int32_t proj[16], int32_t room[16])
|
||||
{
|
||||
if (SC == NULL || !SC->cam_ok) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(proj, SC->cam_proj, sizeof(SC->cam_proj));
|
||||
memcpy(room, SC->cam_room, sizeof(SC->cam_room));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* Replay state */
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@@ -1045,9 +1087,13 @@ void interpPortalPassBegin(const void *dl)
|
||||
{
|
||||
int i;
|
||||
S = NULL;
|
||||
SC = NULL;
|
||||
for (i = 0; i < RP_SLOTS; i++) {
|
||||
if (s_ring[i].valid && s_ring[i].dl == dl) { S = &s_ring[i]; break; }
|
||||
}
|
||||
for (i = 0; i < RP_SLOTS; i++) {
|
||||
if (s_ring[i].cam_ok && s_ring[i].dl == dl) { SC = &s_ring[i]; break; }
|
||||
}
|
||||
s_world_have = 0;
|
||||
s_non_have = 0;
|
||||
s_replay_valid = 0;
|
||||
|
||||
Reference in new issue
Block a user