<ucontext.h> stops with #error unless _XOPEN_SOURCE is defined, so
debug-tools runtime builds on macOS failed at host_sampler.c. The
sampler only needs ucontext_t's machine context, which
<sys/ucontext.h> declares without the deprecated routines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Track an open GP0(A0) upload in the renderer whatever the HD state
(facade gr_vram_upload_begin/commit/set_open): HD activation mid-A0
waits for the commit instead of reading the FBO over received words,
and a full-VRAM load no longer ends the hold; gpu.c restores it from
gp0_state on load and ends it on GP1(01h). Sync the render thread
before flushing dumps or reading HD diagnostics, suppress dumps from
generated frames, use _putenv on MinGW, drop a stale log. Adversarial
fixtures for both cases in full-VRAM and high-resolution-window modes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generated frames are presentation only: their replayed draws skip the CPU
raster and the pack flush, and textured draws resolve the same HD
replacements as the real frame. Drop the HD gate on frame generation. The
HD fixture runs a 30 Hz game with generation off and forced on: real frames
and native VRAM are identical, and every generated frame shows the HD HUD.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A GP0(A0) under HD authority takes the context at its header and keeps it
until the payload commits, so gpu.c's array holds every earlier native draw:
unwritten words keep queued draws and mask checks see the prior native mask.
Publication copies the whole private VRAM (no A0 can be open then), and
render_thread_stop publishes the final private draws before freeing them.
HD replacements and dumping no longer refuse startup or park the thread;
Smooth motion stays off under HD. Adds the review reproduction and #570's
HD safety cases with the render thread running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an HD texture pack the CPU raster is native VRAM's authority, and on
the render thread it writes the private copy: gpu.c's array never saw those
draws, so guest readbacks, mask checks and savestates read stale VRAM. Each
acquire now copies the private result into gpu.c's array (the analogue of
ensure_cpu's FBO readback), leaving the words of a GP0(A0) payload that is
still streaming. Fixture: a recorded fill/draw is visible to a readback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HD texture packs matched draws on the render thread but tracked uploads and
invalidations on the emulation thread, hashing gpu.c's array: a data race
(crash seen in hd_texture_pack_match) and wrong identities whenever the
emulation thread had already recorded a later upload.
The facade now sends residency changes through an optional backend hook
(hd_texture_note). The render-thread backend records them as RTH_HD_NOTE
after the upload/draw they follow; the replay hashes the render thread's own
VRAM copy, which the HD module now follows with the SW rasterizer
(gpu_hd_textures_bind_vram). Pack (re)configuration and HD mode changes are
sync points. Other backends apply the change immediately as before.
The HD GL fixture gains a render-thread case (fails without this change)
and links again: the scale fixture's facade stubs no longer collide with
the real gpu_render.c it includes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dynamic_resolution_min = "display" (game.toml, settings.toml,
PSX_DYNRES_MIN) floors the controller at the selected internal resolution
without the supersample factor, so with supersample > 1 it only trades
supersampling and never renders below the output. psx_dynres_floor_scale
in internal_resolution.h, tested in test_internal_resolution. OpenBIOS
emitter stamp regenerated (config_loader changed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[video] antialiasing_mode = "off" | "fxaa" | "fxaa_hq" (PSX_AA_MODE)
filters the composed game image after the game quad and before hold-last
capture, OSD and swap; guest VRAM, readbacks and the Smooth motion /
frame generation sources are untouched. Off (default) never runs the pass
and is byte-identical to master.
[video] supersample = 1.0..4.0 (PSX_SUPERSAMPLE) multiplies the internal-
resolution target (Match display or a line preset), so the existing area
resolve does ordered-grid SSAA; dynamic resolution takes it as its ceiling.
Debug server: post_aa reads/sets the mode live. Real-GL fixture
gl_post_aa_test; supersample checks in test_internal_resolution.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
debug_server.c is compiled in every build and its debug_key handler calls
psx_debug_toggle_key, which existed only under #ifndef PSX_NO_DEBUG_TOOLS.
Release now gets a stub that reports the keys as unavailable, like
psx_debug_display_aspect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PSX_DEBUG_TOOLS builds only (PSX_DEBUG_TOGGLES=0 turns them off). Keys flip one
rendering feature while the game runs and show the state on the OSD: 1 scale,
2 dynamic resolution, 3 widescreen, 4 Smooth motion, 5 texture filter,
6 wide mirror, 7 geometry correction, 8 perspective texturing, 0 summary,
9 PGXP depth, F10 PGXP colour, F11 PGXP seam, F12 PGXP CPU mode. The
debug_key TCP command presses one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FG_PGXP now names the PGXP feature pass (#558-#560: none / depth / color
/ seam); the reprojection pass turns PGXP depth on with FG_RP_DEPTH=1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[video] frame_generation_method = "redraw" (default) | "reprojection",
PSX_FRAME_GEN_METHOD overrides; replaces frame_generation_redraw.
- Redraw runs and allocates nothing of reprojection: no snapshots, depth
image, programs, HUD matching; the cost-spike clamp and the dynamic
resolution load formula apply to reprojection only. Switching to redraw
or turning Smooth motion off frees what reprojection held.
- Uniform locations and PSX_RP_DEBUG/PSX_RP_NOPREV are read once with the
programs.
- Each method gets its own breaker settings, set again on a switch;
frame_gen_hold takes a kind (step down / over budget), not a string.
- The warp's depth is cleared before the cars are drawn (PGXP depth tested
them LEQUAL against it).
- The warp restores the renderer's texture bindings on units 1 and 2 and
never leaves a sampler on texture 0.
- gl_frame_gen_test: redraw asserts nothing of reprojection ran or was
allocated (its phase 0.5 digest matches master's); reprojection runs
check the warp's pixels on a parallax pan (vs the redraw of the pair, no
holes, HUD exact), real frames unchanged, and no warp depth with PGXP.
- FRAME_GENERATION.md: the key, the default, reprojection and its
R4-tuned object rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A rival car filling half of a 2P view left fewer than half of the pairs
on the camera, rejecting ~7% of 2P game frames; reprojection redraws the
cars itself, so 30% inliers suffice. Overload holds are 0.1-1 s while
in-between frames are reprojected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes the 2P band near the minimap: a 2D sky gradient that shifts with the
camera was treated as a HUD needle and drawn on top. Object-mode road
corners the camera alone explains are warped as world; only textured
triangles that really moved are redrawn as cars, after their view's first
world triangle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Dynamic resolution: Smooth motion's share inflates the measured cost
instead of shrinking the period, so guest-bound detection works again
(it had pinned the level at the ceiling).
- Cost estimate: a sample more than 4x the estimate counts as 4x (one
stall no longer stops generation for many seconds); compiling the
reprojection programs is outside the timed frame.
- Reprojection source: the surface snapshotted at the real frame's present
(two slots: newer and older); the HUD and mirror of both lists are
'keep' in the depth image, drawn last, so a HUD drawn after its list
closed is kept too. Tunnel A/G/B worst ratio 2.9 -> 1.3.
- HUD needles: 2D triangles that move between the frames are drawn at
their in-between position; under them the older frame's dial shows.
- Split screen: a view whose camera cannot be fitted keeps the real image
instead of rejecting the frame (FgCamParams.keep_partial).
- Car redraw skips object triangles before the first world triangle (the
sky dome). Redundant base copies skipped when reprojecting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- `wide` (mode 2) is half a native px above 1x and nothing at 1x, as the
docs and `fine` already said.
- Expansion needs the triangle's depth mode 1 (opaque, 3D, depth buffer on,
not near-camera); without depth, painter order resolves the overlap and a
widened edge would draw over its neighbour.
- gl_renderer_set_pgxp_seam syncs with a live render thread (GL_RT_SYNC)
and counts as a PGXP renderer feature.
- gl_frame_gen_test: seam alone draws exactly like no features; at 1x
depth+seam draws like depth; at 3x it changes the image; the in-between
frame at phase 1 still equals the real one with seam on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[video] pgxp_seam = "off" | "fine" | "wide" (default off; PSX_PGXP_SEAM,
PSX_PGXP_SEAM_PX). Above 1x, a vertex that sits on a neighbour's edge only to
the PS1's precision leaves a hairline crack. Opaque 3D triangles move each
edge outward (mitred, limited at sharp corners): fine = 1 output px, wide =
half a native px; UVs, colour, q and SZ extrapolate with the barycentric
coordinates of the new corners so textures do not slide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gl_renderer_set_pgxp_color_perspective syncs with a live render thread
(GL_RT_SYNC) and counts as a PGXP renderer feature for the per-triangle
depth command. gl_frame_gen_test's PGXP pass runs depth, colour and both:
the scene's SZ now differs per vertex (colour) with rows ordered far to
the right (depth), and the in-between frame at phase 1 must equal the real
frame with each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[video] pgxp_color_correction (default off; PSX_PGXP_COLOR overrides). Gouraud
colour on PGXP 3D triangles interpolates with 1/SZ; textured triangles with
perspective UVs share their w. The flag rides in the per-vertex PGXP code the
depth buffer already carries (sz + 65536).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- gpu.c sends gr_set_depth_triangle only while a PGXP renderer feature is
on (gl_renderer_pgxp_render_wanted): no RTH_DEPTH per triangle otherwise.
- The SZ rides in the textured vertex's unused colour alpha, so TEXV stays
26 floats for every title.
- gl_renderer_set_pgxp_depth / _threshold / pgxp_render_stats sync with a
live render thread (GL_RT_SYNC) like their neighbours.
- A depth-mode change is its own batch-break reason (7); batch_diag returns
all eight reasons.
- Smooth-motion in-between frames replay RTH_DEPTH and test depth from
their own clear; gl_frame_gen_test gains a PGXP pass (overlapping 3D
triangles out of painter order) where the in-between frame must match
the real one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Triangles with a vertex nearer than SZ 1024 (PSX_PGXP_DEPTH_NEAR) stay out of
the depth buffer, the tolerance gains an absolute 48 SZ, and every displayed
frame starts with a cleared depth buffer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 3D batch is two passes: colour tests against depth pulled toward the
camera by 2% of the distance (PSX_PGXP_DEPTH_TOL) and writes none; a
colourless pass then writes the true depth. Lane markings drawn after the
road a hair off its plane stay whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[video] pgxp_depth_buffer (default off; PSX_PGXP_DEPTH overrides) and
pgxp_depth_threshold. gpu.c hands the GL backend each triangle's GTE SZ when
all three vertices are dataflow-precise (gr_set_depth_triangle); the backend
draws those opaque triangles with a LEQUAL depth test and write. 2D,
unproven, semi-transparent polygons, lines and rectangles never test or
write. Depth mode is a batch key and rides in every high-resolution-window,
native-wide and render-thread replay command. Clears are colourless
depth-only draws through the flat batch: after a drawing-area change or a
fill, and when the average SZ rises by the threshold (4096, as DuckStation).
Generated (Smooth motion) frames draw without depth.
Docs: ENHANCEMENTS.md G1.14.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The render-thread controller's load now adds the generated frames actually
drawn in each sample, each at the mean measured cost of the timed ones (GPU
time or CPU wall, the larger), to the real frames' cost. Dynamic resolution
sees the whole render-thread spend: it steps up while there is room for the
real and the in-between frames, and counts the in-between frames against the
budget once they crowd the real ones, without assuming a full panel rate of
them at an estimated cost.
Generation's over-budget pause now looks at the real frames' part of the
load only (the last window's load less the generated frames' share), so the
in-between frames do not switch themselves off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PSX_FRAME_REPORT_MS (the release-build speed readout) now also prints the
emulation thread's busy share (the window less its pacer waits and its waits
on the render queue), the render thread's busy share, the dynamic resolution
level and load, and Smooth motion's generated / real presents per second.
PSX_HOLD_PADS="p1[,p2]" holds raw pad words (active low, 0xBFFF = Cross) on
ports 1 and 2 after every live sample, so a release build (no debug server)
can drive a race from a savestate (PSX_LOAD_SLOT) for timing runs. Unset,
both cost one cached branch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- The .hdres sidecar (version 2) carries the pack's identity, a hash of its
sorted replacement keys (hd_texture_pack_identity); a load with another
pack is refused, as the docs say.
- The learned upload rectangles are bounded by area as well as count: a
restage (load, rewind) re-hashes at most one VRAM's worth of words.
- gl_hd_texture_pack_test: residency is refused with a different pack;
before the load a textured draw shows native artwork, after it the same
draw shows the HD replacement and counts as an applied draw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewind and in-session savestate loads have no sidecar. Each Beetle-keyed
upload rectangle seen in the session is remembered (at most 256); after a
restage it becomes resident again only if the restored words still hash to
a replacement key, so the HUD keeps its replacement across loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A savestate restage cleared every upload identity, so a texture uploaded
once (a race HUD) lost its replacement after any load until the game
uploaded it again. Saving a slot now also writes the Beetle-format residency
beside it (*.hdres: host metadata, never in the .pst), bound to a CRC of
the whole native VRAM. A load restores it only over exactly that VRAM, so
no identity is fabricated; anything else falls back as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manifest check on `default` is text-only; a symlink the package ships
could still point outside it. The default now resolves only when it exists
with the resource's kind (folder / file) and its canonical path stays
under the package's canonical root; otherwise the resource is unset and a
required one blocks launch. mod_packages_test covers a cleared or removed
selection restoring the default, a missing default folder, and a default
that is a symlink out of the package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- 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>
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>
Variable refresh (G-Sync/FreeSync, ProMotion): present each frame when it is
ready (swap interval 0). Smooth motion targets the panel's maximum refresh at
the current size (psx_sdl_display_max_refresh) and never exceeds it.
PSX_HOST_REFRESH_HZ / PSX_HOST_REFRESH_MAX_HZ simulate a display for headless
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #561. Guest time now has two entry points (psx_cycles.h): the
CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion,
i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale,
and the unscaled device advance psx_advance_cycles() (DMA, idle skips,
mul/div and GTE deadline waits). The emitters' block-mode charges emit
psx_cpu_charge; overlay DLLs accumulate it raw and publish through the
cpu_charge callback (was advance_cycles, same slot) into the same host
function, so nothing is scaled at the DLL boundary. The batched
psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache
refills and bios_hle.c all charge through it. At scale 1 it is exactly
psx_advance_cycles.
Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged
at every VBlank edge (deterministic, works online, no mod needed), plus
psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or
to tie the scale to a mod (guest_cycle_scale_gated = true; every mod
session reset shuts it). The carried fraction and both gate states are a
boot_state section (BS_SEC_GCS, written only when the scale is not 1), so
savestates and the netplay rollback snapshot re-simulate the same cycles.
psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback
re-simulation against the real psx_cycles.c.
OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No CPU is emulated: recompiled code charges the guest clock a fixed cost
per instruction. guest_cycle_scale = N charges 1/N of it (instruction,
load, mul/div, GTE latencies, overlay DLL batches) while VBlank, timers,
CD, SPU and DMA keep hardware time, so the game never runs out of frame
time. A title constant in game.toml (1 = faithful, 1..64), never a player
setting; guest_cycle_scale_gated applies it only while the title's mod
opens the gate (psx_guest_cycle_scale_gate_open). PSX_GUEST_CYCLE_SCALE
overrides for testing. A scale other than 1 is folded into the netplay
content fingerprint as a safety net.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>