A full-screen rect backdrop that stops short of the reveal vetoes polygon overhang (Tomba village, beads-eio.4.28).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Under HD texture-pack authority (and dual-raster netplay) every draw was
also rasterized into CPU VRAM unless a render pass was active. Replayed
draws of a generated frame were not exempt, so each in-between frame wrote
interpolated geometry into the guest's native VRAM. They are presentation
only, like a pass. Fixture: compose under HD authority, VRAM unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under HD texture-pack authority (and dual-raster netplay) every draw was
also rasterized into CPU VRAM unless a render pass was active. Replayed
draws of a generated frame were not exempt, so each in-between frame wrote
interpolated geometry into the guest's native VRAM. They are presentation
only, like a pass. Fixture: compose under HD authority, VRAM unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Log lines and doc headings say "Smooth motion (frame generation)", the
player-facing name. Config key [video] frame_generation and PSX_FRAME_GEN
are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
render_thread, present_thread and frame_generation are read from the
player's settings.toml [video] (over the game.toml default; PSX_* env
still overrides one run and is never saved), written back by
save_user_settings, and round-tripped through the recomp-ui Display rows
when the launcher defines RECOMP_LAUNCHER_HAS_RENDER_PIPELINE. The
pipeline starts once at boot: a first-boot launcher choice applies
immediately, an in-game change is saved and applies at next launch.
Regenerated the OpenBIOS emitter stamp for the config_loader change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gpu.c's precise-triangle path references Smooth motion's vertex sources;
the software-only fixture never reaches them (abort if it does).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gpu.c asks the GL backend and gte.cpp for Smooth motion's vertex sources;
master's native_wide_projection_test (cd9981cd) links gpu.c with these stubs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Corner and tunnel runs showed light cracks along seams and near the view
edge in generated frames:
- weld: every placed vertex at one screen position of a view moves alike
(the same corner arrives from several projections);
- a vertex without a projection on an edge between placed vertices moves
as that point of the edge (T-junctions from the game's polygon splits);
- a vertex the in-between camera passes is projected at a clamped depth,
and such a game frame is not generated (no near-plane clipping: tunnel
ceilings, overhead bridges);
- debug: "clamped", "guessed" (CPU-built vertices placed by a triangle
mean over a >1 px motion spread).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
Both swap paths go through gl_window_swap. The temporal-blending ownership
guard allows the present thread's context (pt_begin only). The frame
generation fixture used setenv, which MinGW lacks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diagnostic: a staged present_shot waits for the next generated frame, so the
in-between images can be inspected in game.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Armed at init (session start, render-thread start, a new resolution) and on
a savestate load, game entry or window resize. While armed, an overrun jumps
straight to the highest level whose pure-area prediction is at or below
0.78, then is judged like any step; a fit after a step, an up step, a failed
judgement or 6 s disarm it and single steps fine-tune. A fixed cost only
makes the target dearer than predicted, so a jump never lands below the
level that fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opt-in under the render thread. Composed frames go to a ring of offscreen
slots at the window's size (binds of framebuffer 0 on the composing context
land on the current slot); a present thread with a second, shared context on
the same window waits on the slot's fence, copies it 1:1 and swaps. The
compositor's wait moves off rendering; the composing side waits only while
every slot is queued or on screen. Falls back to direct swaps when the shared
context or sync objects are unavailable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
master's facade now calls gpu_hd_textures_begin_upload/begin_copy/end_copy;
the fixture runs without a pack, so it stubs them like the other hooks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
master's HD texture packs (#557 base) added gpu_hd_textures_* calls to the GL
renderer and the gpu_render.c facade; the fixture runs without a pack. The
command index is regenerated for frame_gen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[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>
A step grows the scratch to hold the old level's displayed rect and draw
area, and it stayed that size: every tiled stencil rebuild renders into it,
so on GL-on-Metal its size cost bandwidth on each one. Return it to the size
a surface allocated at the new level starts with.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hr (VRAM) surface stayed allocated at the ceiling. On GL-on-Metal every
render pass loads and stores the whole attachment, so its cost followed the
ceiling rather than the level: R4 2P VS at 5x ran 40-45 Hz for its first
seconds with a 10x ceiling and 60 Hz with surfaces allocated at 5x. Allocate
a new hr surface at each step and reseed it from the 1x image and the saved
rects; a failed allocation falls back to the in-place step down, or refuses
a step up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A native-wide surface was allocated at the dynamic-resolution ceiling and
rendered into its lower-left corner. On GL-on-Metal every render pass loads
and stores the whole attachment, so the wide surfaces cost the ceiling's
bandwidth at every level. Allocate them at the level they render at and
reallocate at each step, rescaling the presented rows and margins from the
old surface. The limit check stays at the ceiling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The step record's replay sat inside the debug-tools block, so release
builds dropped every recorded step. gl_render_thread_test gains a dynres
mode: the same level steps between frames with the thread off (applied at
once) and on (recorded), identical guest readbacks, VRAM, frame and wide
surface, and the render thread's frame cost measured. Also drop the
duplicate gpu_ws_background_requires_full_composite stub the #508 merge
left in the scale-invariance fixture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
master's facade now calls gpu_hd_textures_begin_upload/begin_copy/end_copy;
the fixture runs without a pack, so it stubs them like the other hooks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
master's HD texture packs (#557 base) added gpu_hd_textures_* calls to the GL
renderer and the gpu_render.c facade; the fixture runs without a pack.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
render_thread_test is registered on MinGW (winpthreads provides the
fixture's pthreads and nanosleep; the core takes its Win32 path).
gl_render_thread_test gains the MinGW link path run_gl_scale_invariance.py
uses on test/gl-scale-invariance-windows (static SDL3, opengl32 and its
Win32 libraries, LTO, probe.exe), and a host with no window or GL 3.3 core
context (headless, Windows over SSH) exits 77 from a preflight run, reported
by CTest as a skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty CPU->VRAM rect fell through to glb_vram_transfer_in on the
emulation thread while the render thread owned the context; it is now
recorded like any other upload. A projected triangle whose record cannot
be reserved takes the context first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--headless-opengl captured the presented image into the ring every frame
through gl_renderer_capture_display_hires, a sync point, so a headless
run (fp_identity) held the context for most of every frame. The capture
is now recorded at the same point of the frame (gl_renderer_ring_capture)
and done by the render thread; ring readers already sync first. A rect the
GL surface cannot serve is skipped there rather than resolved through a
CPU readback, and ensure_cpu refuses to run on the render thread at all:
its readback would land in the private upload source and mark the CPU
side current without gpu.c's array seeing the pixels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OSD query at the two present-skip sites now goes through
ov_needs_present(), which falls through to host_osd_needs_present()
unless the render thread is replaying a present with its recorded
overlay state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>