Commit Graph

2690 Commits

Author SHA1 Message Date
Shokunin e3399ee951 Docs: dynamic resolution on the render thread
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:52:00 -07:00
Shokunin ac799cbd8c Dynamic resolution on the render thread: replay recorded steps in every build; GL equivalence test steps the level
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>
2026-10-07 12:51:55 -07:00
Shokunin 4b2c356a14 Render-thread dynres: cut the soft hold tails to half a second
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:51:55 -07:00
Shokunin ecb5e96171 Render-thread frame cost: GL_TIME_ELAPSED (timestamps read 0 on macOS); frame_perf stands aside
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:51:55 -07:00
Shokunin cb1e445e46 Dynamic resolution on the render thread: recorded steps, per-frame render cost, render-thread controller
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:51:55 -07:00
Shokunin c09871b5e0 Test: render thread GL fixture stubs the HD texture pack hooks
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>
2026-10-07 12:51:43 -07:00
Shokunin 2bc29cde64 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:50:16 -07:00
Shokunin 98c90db5f4 Docs: the render thread builds and its tests run on Windows (MinGW)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:50:04 -07:00
Shokunin eabc8ed7f5 tests: build and run the render thread tests on Windows (MinGW)
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>
2026-10-07 12:50:04 -07:00
Shokunin 5860b85234 Render thread: no direct GL call while recording, even for empty uploads
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>
2026-10-07 12:49:53 -07:00
Shokunin f40466f64d Docs: queued debug captures, ensure_cpu on the render thread, line refs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:53 -07:00
Shokunin 26b0537ff2 Render thread: queue the headless presented-image capture
--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>
2026-10-07 12:49:53 -07:00
Shokunin c74e9eb448 Test: present-skip guard follows the overlay accessor
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>
2026-10-07 12:49:53 -07:00
Shokunin 414feef650 Docs: render thread QoS note
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:53 -07:00
Shokunin 992d3cfe73 Render thread: user-interactive QoS on macOS
A default-QoS pthread is scheduled onto efficiency cores when the host is
busy, which put the render thread, now on every frame's critical path,
behind the emulation thread it is supposed to relieve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:53 -07:00
Shokunin 46f2abebcf Docs: render thread design, sync points, interoperation, tests
docs/RENDER_THREAD.md: the record/replay model, why guest-visible VRAM
stays gpu.c's array (private upload source on the render thread), the
guest state captured at record time, every sync point with file:line,
the frame bound and present skipping, interoperation with savestates,
netplay, render passes, interpolation, widescreen, resolution changes,
screenshots, headless and Vulkan, what is out of scope, and the tests.
TCP_COMMANDS.md documents render_thread (index regenerated);
config_schema.md documents [video] render_thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:53 -07:00
Shokunin 8f293b29be Render thread: never skip two presents in a row
R4 races present new content every other vblank, so at a saturated 4K
widescreen frame two vblanks behind was common and about half the
presents were dropped. Skipping is a catch-up step, not a frame-rate
policy: one skipped present is always followed by a shown one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:47 -07:00
Shokunin 9abcff08a4 Render thread: skip a present only when two newer frames wait
One frame behind is the normal pipelined state when the render thread is
the slower side; skipping on it dropped nearly every present at 4K
widescreen. rt_frames_ahead() counts the complete frames queued after
the one being replayed; the backend skips only at two, where the
emulation thread is at the in-flight bound waiting on the render thread.
The unit test checks the count never exceeds the bound and that both
cases occur under a slow consumer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:47 -07:00
Shokunin 831f5fa03e OpenGL render thread: fit_wide_aspect is not a sync point
main.cpp's adaptive aspect asks it every native-wide frame; it only does
arithmetic on the driver limit and the internal scale, which change only
while the emulation thread holds the context. As a sync point it held
every widescreen frame on the emulation thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:47 -07:00
Shokunin 5a526537d1 Test: render thread on vs off on a real OpenGL context
gl_render_thread_test runs a scripted stream through the facade twice per
scale (1x, 4x): every value the guest reads back, native VRAM, the frame
at internal resolution and the native-wide surface must match with the
render thread off and on. The stream has mask set/check, a mask-checked
upload (per-pixel readback sync point), back-to-back uploads of one rect
with a catch-up between gpu.c's writes and its commit, ring wraps,
readback frames, presents, and widescreen tags that change per primitive
and are poisoned after each call. A slow render-thread present keeps the
replay a frame behind. It fails for each of: reading tags live, sharing
the upload source with the guest array, dropping the stream state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:47 -07:00
Shokunin af0e6a0472 Frontend: [video] render_thread option, vblank boundary, render_thread command
[video] render_thread (default false; PSX_RENDER_THREAD=0/1 overrides,
PSX_RENDER_THREAD_FRAMES bounds frames in flight, default 2) starts the
OpenGL render thread at the first vblank, after the context and every
startup GL call exist. Each vblank then closes a frame right after its
present and before wall-clock pacing, so the render thread draws while
the guest waits. Netplay, frame interpolation, Vulkan and software keep
the synchronous path.

TCP: render_thread reports records, frames produced/consumed, presents
and stale presents skipped, sync points with their recent reasons,
backpressure and render busy time. Display-ring and present-image-ring
readers sync first (the render thread fills them).

config_loader is a BIOS emitter input: OpenBIOS fingerprint regenerated
(tools/regen_bios.sh --config bios/OpenBIOS.toml; the C is unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:42 -07:00
Shokunin f1430d6752 OpenGL backend: record on the emulation thread, replay on the render thread
With the render thread running, the backend's facade table records every
state change, primitive, fill, copy, CPU->VRAM upload (payload copied)
and native-wide call, and the present, into the command ring; the render
thread replays them through the unchanged glb_* functions. With it off
gl_backend_get returns the existing table and nothing new runs.

Sync points: every other GL entry point reached on the emulation thread
(readbacks, savestate restage, render passes, OpenXR, settings,
diagnostics) drains the ring and takes the context (GL_RT_SYNC); the
emulation thread then runs the rest of that frame directly, exactly as
without the thread, and hands the context back at the frame boundary when
the frame may run asynchronously again (no depth24 display, dual-raster,
frame interpolation, render pass, netplay or OpenXR).

Guest-visible VRAM stays gpu.c's array: while the render thread owns the
context, uploads stage from a private copy that receives each payload in
command order. Guest state the backend reads while drawing (per-primitive
widescreen tags, the native-wide fast-path latch) is captured at record
time; host overlays travel with the present; a present that a newer
recorded frame already supersedes is skipped. The emulation thread answers
projective/wide-present/draw-area queries from mirrors of the recorded
calls. The per-frame display-ring readback is queued (fbo_peek_deferred)
instead of being a sync point.

The GL fixtures link render_thread.c and stub the two facade hooks; the
scale fixture also gains the gpu_ws_background_requires_full_composite
stub it was already missing (it failed to link on master).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:31 -07:00
Shokunin 01f9cd34d6 Render thread core: SPSC command ring, frame bound, sync-point hand-off
Backend-neutral core for the opt-in [video] render_thread: the emulation
thread appends records to a single-producer/single-consumer ring and the
render thread replays them in order with the graphics context current.
rt_frame_end bounds closed frames in flight (backpressure), the render
thread can tell a frame is stale, and rt_acquire is the sync point:
drain, then move the context to the emulation thread until rt_release.
No lock is held while a record executes.

render_thread_test drives it with a fake executor and context token:
ordering and payload integrity across ring wraps, oversize refusal, the
in-flight bound under a slow consumer, stale detection, randomized
acquire/release interleaving and stop while held (TSan clean).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:49:14 -07:00
Shokunin db01fe017e Queue native-wide mirrors on the full-VRAM surface
Each mirrored batch switched to the wide surface and back, ending two
Metal render passes over the hr (10240x5120 at 10x) and wide surfaces;
cost grew with the allocation, so dynamic resolution's down steps did not
lower it. Mirrors now ride the windowed mode's queue (one pass per wide
surface, at the same sync points: uploads, pack, clears, stencil
rebuilds, render-pass copies, presents and dumps). GL_LINES mirrors (1x)
flush it and draw at once. Queue entries carry their scissor and palette
texture.

PSX_GL_WIDE_QUEUE=0 and PSX_GL_WIDE_STENCIL_FULL=1 restore the previous
paths for A/B; gl_scale_invariance_test gains a "wmask" mode (wide mask
stencil, centre splice on/off) and checks native VRAM, frame at S and the
wide surface are identical with either off, for wmask, lines, twin and
the scene at 1x/3x/9x and in the window mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:47:54 -07:00
Shokunin 35181c1a6c Rebuild only the stale part of native-wide mask stencils
At S > 1 every GP0(E6h) mask-check enable after an unchecked opaque draw
rebuilt each native-wide surface's whole stencil from alpha, tiled. At a
10x allocation that is many render passes over 4270x5120 surfaces per
enable; R4 2P VS enables the check several times a frame and spent ~85%
of its emulation thread waiting on Metal command buffers there (16:9 at
10x: 17.7 guest Hz).

Track per wide surface the rect where the stencil may differ from alpha
(unchecked mirror draws by bbox, the centre splice, scale-step rescales,
render-pass restores) and rebuild only that. While the centre is spliced
from hr at every present its stencil is never observed, so the centre
part is left stale and rebuilt only if a mask-checked mirror draw meets it
with the splice off. Native (1x) keeps the whole-surface rebuild.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:47:54 -07:00
Shokunin 024c7bb3b6 Regenerate the OpenBIOS emitter stamp
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:47:54 -07:00
Shokunin 44e452ae84 Keep one owned GL swap with dynres telemetry 2026-10-07 12:47:54 -07:00
Shokunin 96ee5c0650 Round-trip dynamic resolution through launcher settings 2026-10-07 12:44:59 -07:00
Shokunin ecbbb5eceb Add the opt-in dynamic resolution controller, config and telemetry
A step controller (dynamic_resolution.c) lowers or raises the GL internal scale from frame-time telemetry between a floor and the configured ceiling; off unless a title or the player enables it. Tested by dynamic_resolution_test (synthetic load) and the real-GL gl_scale_invariance_test step modes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:44:59 -07:00
Shokunin c9eaeb04d3 Change the GL internal scale at run time (opt-in)
gl_renderer_set_dynamic_resolution(1) keeps the hr surface and the
native-wide surfaces allocated at the scale context init gave them (the
ceiling) and lets the internal scale step to any integer level from 1 to
it while the game runs. A lower level renders into the surfaces'
lower-left VRAM*S corner; every viewport, scissor, pack, copy and capture
already takes the scale at use, so only allocation and the present's
normalised UVs read the allocated size. Off (the default) nothing is
allocated differently and no step can run.

A step runs after a present, never inside a render pass:
- it takes the top-left sample of every native block of the old surface
  (the sample the pack reads) into a 1x image and reseeds the new corner
  from it, so the guest-visible VRAM is unchanged byte for byte;
- it rescales the displayed rect and the draw area from their old pixels,
  so neither the frame on screen nor the one being drawn shows 1x blocks;
- it rescales the native-wide margins in place, in row bands ordered so
  no source row is overwritten before it is read;
- it marks the mask stencil stale (rebuilt at once while mask checking is
  on) and drops the render-pass backup and images.

Reallocating per step instead measured 30-106 ms at 7x-10x on an Apple
M4, mostly the first touch of the new surface. Steps are not offered in
the windowed high-resolution mode or with dual raster.

gl_scale_invariance_test gains "steps" runs: rendering after a step into
a ceiling-sized surface equals the fixed-scale run at that level; a chain
of steps (9x down to 1x and back, 5x) leaves the native VRAM unchanged at
every step, the two rects and the wide margins exact nearest rescales of
their old pixels, everything else the old top-left samples, and mask
checking right after a step correct; the window mode refuses steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:44:46 -07:00
Matt Stanley 764a645a40 Merge pull request #557 from RetroPortingToolKit/review/pr502-netplay-20261007
Integrate validated controller and netplay stack with compatibility fixes
2026-10-07 12:28:10 -07:00
Matthew Stanley fc8c58135c Test bounded screen tag reads, packet reuse and widescreen isolation 2026-10-07 12:22:25 -07:00
Matthew Stanley 62d5277174 Add guarded screen-space and tiled-strip widescreen tags 2026-10-07 12:06:57 -07:00
Matthew Stanley 3a11aea16d Refresh netplay validation against master retaining audited startup cache and local views 2026-10-07 11:59:16 -07:00
Matthew Stanley 9a1bfdcee2 Merge PR 554 for local netplay validation; clear new peer state on rematch 2026-10-07 11:21:55 -07:00
Matthew Stanley 7d0a18ebc6 Integrate netplay pad and own-view stack without dropping current-master behavior
Preserve negotiated content and dual-raster pass gates. Retain both fixture assertion sets and HD-off seams. Regenerate both BIOS with actual native emitter. Fix type-only rollback corrections and exclude extra-pad changes from digital-only promote gates.
2026-10-07 11:06:57 -07:00
Matt Stanley d3b22491c6 Merge pull request #556 from RetroPortingToolKit/integration/startup-cache-landing-20261007
Land reviewed warm startup stack with current texture-pack work
2026-10-07 11:02:05 -07:00
Matthew Stanley dea71f2261 Integrate verified duplicate LOAD guard into current netplay validation bundle 2026-10-07 10:52:06 -07:00
Matthew Stanley 62ee64d0a3 Merge reviewed validated warm startup cache (PR555)
# Conflicts:
#	runtime/src/mod_packages.cpp
#	runtime/src/mod_runtime.cpp
#	runtime/tests/test_mod_runtime.cpp
2026-10-07 10:51:41 -07:00
Matthew Stanley 14001565cf Merge reviewed launcher worker capability opt-in (PR553) 2026-10-07 10:49:04 -07:00
Matthew Stanley ee140c9519 Merge reviewed sky scheduler opt-in (PR552) 2026-10-07 10:48:55 -07:00
Matthew Stanley a99ad54763 Merge reviewed host launch timing scopes (PR551)
# Conflicts:
#	runtime/src/mod_runtime.cpp
2026-10-07 10:48:55 -07:00
Matthew Stanley 5549b2d426 Merge reviewed UNC path preservation (PR550) 2026-10-07 10:48:22 -07:00
Matthew Stanley c51f191c0b Merge reviewed render provenance and sky scheduler fix (PR546) 2026-10-07 10:48:15 -07:00
Matthew Stanley 3d31bcebd4 Merge commit 'f0a8c6194f807b0f8b1b02bdefdeb4c8025c5977' into review/pr502-input-20261006 2026-10-07 10:01:38 -07:00
Matthew Stanley c6b089b896 Combine controller stack with validated PGXP fixes and current master; regenerate both BIOS backends 2026-10-07 10:01:37 -07:00
Matthew Stanley f0a8c6194f Deduplicate GL fixture seams after current-master integration 2026-10-07 09:59:39 -07:00
Matthew Stanley d5d9a65889 Integrate current master retaining strict GP0 fixture guards 2026-10-07 09:55:51 -07:00
Matthew Stanley 928cde6645 Set audited fixture cache through the CRT environment 2026-10-07 09:31:47 -07:00
Matthew Stanley 29a9e8d598 Contain best-effort preload failures and isolate audited cache tests 2026-10-07 09:30:57 -07:00