Commit Graph

11 Commits

Author SHA1 Message Date
Matthew Stanley 62d26bff82 Fix redraw policy, HD reload residency, AA ownership and FG budget publication
Address objective review blockers in #563, #567, #576 and #580 on the combined renderer stack. Preserve redraw/default policy, bounded same-pack learned uploads, GL context ownership, and coherent current-window budget accounting. Add focused CPU, real-GL, concurrent-publication and authority-transition regressions.
2026-10-08 11:16:22 -07:00
Matthew Stanley c87b8deab6 Merge commit '33a4ff56b0f769cf37f2804d00dce2e4cb106408' into integrate/pr558plus-fixes-20261008
# Conflicts:
#	runtime/include/frame_gen.h
#	runtime/src/gpu_gl_renderer.c
#	runtime/src/main.cpp
2026-10-08 11:00:53 -07:00
Shokunin b0a89e4ede Smooth motion: in-between frames by reprojecting the real frame
Each in-between frame warps the newer real frame's finished image by the
in-between camera instead of drawing the scene again:
- per game frame: a snapshot of the list's finished image (taken when the
  list closes; R4 flips to a buffer before drawing into it, so the live
  surface is not a stable source) and a 2x native image of camera 1/z,
  pixel kind and view rasterized from the recorded triangles in painter
  order (world / car-or-dome / 2D-HUD-small-view);
- per in-between frame: one grid draw (a vertex per native pixel) that
  unprojects world pixels with their depth, moves them by the view's
  in-between camera (fg_view_affine) and samples the snapshot at full
  resolution, nearest wins; cells over depth edges or non-world pixels are
  left to the snapshot underneath; 2D/HUD pixels are put back; cars are
  drawn again at their own in-between position.
Measured: ~0.3-0.8 ms CPU and ~0.3-2 ms GPU per in-between frame (was 2-4
ms each), A/G/B max out-of-range 0.3-3 %, worst ratio 1.1-1.5.
[video] frame_generation_redraw / PSX_FRAME_GEN_REDRAW=1 keeps the old path.
Planner/holds: dynres 'over budget' no longer stops cheap reprojected
frames; guest-late trips ignored while they are cheap; allocation and
shader compilation are kept out of the cost estimate. frame_gen JSON:
rp_ms, rp_zn, reprojected, np (why a flip planned none); debug buffer 4 KiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:39:42 -07:00
Shokunin 705b8d8c86 Smooth motion: draw the first in-between frame while a third of the budget is free
fg_plan drew the first in-between frame (to measure its cost) only while the
real frame left half the budget. On Windows with the display ring, R4's 30 Hz
real frame costs 2 x 7.2 ms of 33.4: the first frame was never drawn, its cost
never measured, and Smooth motion generated nothing (0 frames/s; macOS, at
2 x 5.7 ms, was under the line). A third of the budget is enough.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:51:35 -07:00
Shokunin 8948e9584c Smooth motion: plan the frames the clock shows; vrr in settings.toml; atomics
- settings.toml parses [video] vsync = "vrr" and a launcher save writes it
  back as "vrr" (was dropped / saved as "on"); every mode round-trips in
  video_enhancement_settings_test.
- fg_slots counts the display intervals the clock (fg_clock_phase) fills,
  with the same 0.985 phase limit. fg_plan_hz and the dynamic resolution
  share use it: 59.94 Hz content on a 60 Hz panel (2.002 intervals) plans
  and budgets one in-between frame, as shown, not two.
- The real share read on the emulation thread uses atomics: s_fg_flip_vb,
  s_fg_swap_ema and a published copy of the generated-frame cost estimate.
- fg_hud_lerp accepts a match at the 8th 2D triangle of the window.
- docs/FRAME_GENERATION.md: the plan's slots and the vrr mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:34:36 -07:00
Shokunin 0bebface12 Smooth motion: budget dynres for the in-between frames, time them from the game frame's arrival, keep small views put, move HUD needles
- Dynamic resolution budgets the real frames with the share of the game
  frame Smooth motion leaves them (gl_renderer_frame_gen_real_share), so the
  scale drops until the in-between frames fit the display refresh.
- In-between frames are timed from the game frame's arrival, not from the end
  of its composition; the last one per game frame is no longer dropped by a
  quarter-interval margin.
- Views under 15 % of the largest (a rear-view mirror) stay as the real frame
  drew them: a wrong mirror camera fit threw its lane marks across the
  screen as a dashed line.
- HUD: 2D triangles both frames draw with the same key that moved a little
  (tachometer needle) are interpolated; changed digits stay as drawn.
- frame_gen JSON: end_ahead / end_bp / end_phase counters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:49:54 -07:00
Shokunin 3c64a13347 Smooth motion at any refresh; keep matching after a display-size change
In-between frames go on one global grid at the display refresh (or the
VRR panel maximum), never faster, across game frames; the interpolation
phase comes from the host clock, so 90/100/144/165/240 Hz and other rates
that are no multiple of 30 work. Up to 15 generated frames per game frame.

Fix: a display buffer that changed size at the same origin (e.g. the first
frame after a save-state load) left a stale entry that every list closed
with, so no two frames ever matched and Smooth motion stayed off for the
rest of the session. The entry is replaced now. frame_gen JSON reports
why the last flip planned nothing (noplan_why).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:49:54 -07:00
Shokunin dc4197c053 Frame generation: in-between frames from an in-between camera
Pairing triangles of consecutive draw lists by key and draw order paired
the wrong ones whenever the game reordered, culled or switched LODs (R4:
a grandstand of one repeated texture turned into a staircase; on a
synthetic version 225 of 226 pairs were wrong). No primitive is paired by
draw order any more.

gte.cpp notes each RTPS/RTPT output (only while generation is on) with an
identity (issuing ra + model-space vertex), its camera-space position and
H, indexed by screen word; gpu.c records each polygon vertex's source
ahead of the triangle (RTH_FG_SRC, never drawn). Per view the camera
motion between the two frames is fitted from the paired camera-space
vertices (RANSAC + least squares); one real frame is redrawn from the
in-between camera: world vertices re-projected, vertices moving against
the world (cars) lerped in camera space, CPU-built vertices moved with
their triangle, HUD/2D unchanged. Moving forward the older frame is
redrawn; uncovered areas show the newer real frame. A verdict rejects
the generated frame (the real one shows) without projections, with few
pairs/inliers, or with a motion no camera makes. Real frames are
unchanged (gl_frame_gen_test).

PSX_PRESENT_SHOT_BURST=N captures the next N composed frames, real and
generated, for checking in-between frames against their neighbours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:57:02 -07:00
Shokunin 0269264da2 Frame generation: plan ceiling for render-thread overload, stalls judged by wait
A render thread a frame behind lowers the plan's ceiling by one (recovering
one per 2 s) instead of tripping the breaker; the guest waiting on the queue
trips it only when the wait was a stall (a quarter of the game frame). The
real-frame cost plans with CPU time; the GPU span is reported only. The
generated-frame blit copies colour only and marks the stencil stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:57:02 -07:00
Shokunin 816d8e47f4 Frame generation: warm cost estimate, re-probe, guest-paced breaker 2026-10-07 12:57:02 -07:00
Shokunin 4f97482f8c Frame generation on the render thread from recorded draw lists
[video] frame_generation (opt-in, with render_thread): the render thread keeps
each game frame's replayed draw list, matches the triangles of the last two
(frame_gen.c), and presents in-between frames at the display refresh by
redrawing the newer list into separate surfaces with matched vertices
interpolated. It never runs guest code. Real frames are composed at their
flip and presented after the generated ones, with the same pixels and
order. Generation uses only measured surplus: a plan from render-thread CPU,
generated-frame cost and swap time, a breaker after backpressure or late
guest frames, and a pause while dynamic resolution is over budget. The
render-thread core gains a timed tick hook. Frame-cost queries pause around
generation, so dynamic resolution measures real frames only.

Tests: frame_gen_test, render_thread_test (tick), gl_frame_gen_test (real
frames identical off/on; phase 0/1 equal the endpoints). Docs:
docs/FRAME_GENERATION.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:53:09 -07:00