Commit Graph

689 Commits

Author SHA1 Message Date
Matthew Stanley 50958497c4 Integrate merged experimental wheel support before preset validation 2026-10-15 13:49:46 -07:00
Matthew Stanley a6ee3aa062 Integrate current master and canonical BIOS stamp for PR609 validation 2026-10-15 13:40:27 -07:00
Shokunin 8be777f1f1 quality presets: a supported key set, host-side Custom classification
- [quality.*] accepts only the supported keys (quality_key_supported):
  twelve player-editable ones (masked, copied, classified, applied) and
  nine preset-only ones (no settings.toml form; applied on select). Any
  other key, e.g. vsync or renderer, is a load error.
- Picking a preset now also applies bloom, texture LOD, anisotropy, FMV
  chroma smoothing and dithering (quality_apply_video_globals).
- The frontends flag Custom on any tracked row; the host counts only rows
  of keys the title governs (psxq::launcher_custom_edit), so Antialiasing
  no longer turns Ultra into Custom when no preset sets it.
Tests cover the rejection, every player key's masking/copy, and the
classification cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:57:27 -07:00
Matthew Stanley 6d3c5308a7 Integrate current master and canonical BIOS stamp for preset re-review 2026-10-15 08:34:17 -07:00
Matthew Stanley 9f88c128f4 Preserve the selected preset base when launcher edits become Custom 2026-10-15 08:33:23 -07:00
Shokunin ab49f294e9 quality presets: one preset/key-ownership transaction for every settings save
The boot launcher, soft-return launcher and the in-game menu's live
settings now save settings.toml through psxq::settle_save:
- a one-run PSX_QUALITY override never persists: governed keys and the
  quality_* keys come from disk, so the previous preset or Custom values
  survive even when the boot seed was built empty;
- a governed live edit (psxq::live_edit) or launcher Custom becomes
  Custom from the current base, with the edited values, and survives
  restart instead of being masked away;
- a launcher pick records the preset; unrelated edits keep it.
Tests reproduce both review cases through a settings.toml round trip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:05:25 -07:00
Matthew Stanley d110d0a423 Integrate current master for reviewed PR597 and retain canonical BIOS stamp 2026-10-15 00:36:07 -07:00
Matt Stanley d550ddf2e9 Merge pull request #590 from RetroPortingToolKit/feat/idle-skip-store-counters
idle_skip: elide store-counter wait loops (PsyQ v_wait), opt-in
2026-10-15 00:34:00 -07:00
Matt Stanley 9521343200 Merge pull request #664 from RetroPortingToolKit/fix/menu-host-followups
Menu host follow-ups: restart bits for touched groups, local return, recomp-ui default provider
2026-10-15 00:33:44 -07:00
Matthew Stanley d946aec09a Integrate current master for reviewed PR597 and retain canonical BIOS stamp 2026-10-15 00:00:06 -07:00
Matthew Stanley 80674e4d9f Integrate current master for reviewed PR593 and retain canonical BIOS stamp 2026-10-14 23:59:50 -07:00
Matthew Stanley 2d2c1849f3 Integrate current master for reviewed PR591 and retain canonical BIOS stamp 2026-10-14 23:59:13 -07:00
Matthew Stanley e0800a28e0 Integrate current master for reviewed PR590 and retain canonical BIOS stamp 2026-10-14 23:58:57 -07:00
Matthew Stanley dfd4555d09 Include fixed scaled-charge batching profile in netplay compatibility 2026-10-14 23:51:49 -07:00
Matthew Stanley 2947916215 Detect active gamepad sessions without hijacking Deck Desktop Mode 2026-10-14 23:49:26 -07:00
Shokunin c3a2f2c068 Menu host follow-ups: restart bits for touched groups, local return, default provider
- Restart bits: a group waits only when the edit touches it (next differs
  from the live value) and the result differs from boot. Dynamic
  resolution moves the live internal resolution off boot, so an in-game
  texture filter change relaunched the game. test_menu_video_snapshot
  covers both cases.
- Return to the frontend from a local game opens the launcher as at
  boot; only a lobby match start (the runtime's pending-launch exit, or
  the provider's lobby view asking once its match started) keeps the
  launch and resumes the room.
- Default provider: with no title choice, the UI library's provider is
  used when it ships one (recomp_ui_psx_menu.h), so recomp-ui stays the
  default; PsxMenuHost.default_selection tells it to keep the title's
  default presentation.
- Debug: menu_host reports provider, open, pause, capture and snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:17:18 -07:00
Matt Stanley d3db61b199 Merge pull request #598 from RetroPortingToolKit/feat/host-pause
Host pause API (in-game overlay P1)
2026-10-14 11:55:36 -07:00
Matthew Stanley f4c5d6c93f Draw paused host menus even before the first held game frame 2026-10-14 11:47:04 -07:00
Matthew Stanley 934e6c96b6 Synchronize shared menu video and netplay getters across runtime threads 2026-10-14 11:29:13 -07:00
Shokunin 0d096e2858 GPU: carry the depth24 held-VBlank flag in the snap; executable hold regression
Review follow-up on #661 (#636):
- test_depth24_present_hold_vblank.py reads sources as UTF-8 (cp1252 on
  native Windows Python raised UnicodeDecodeError).
- The present skip is now guest state: set at guest VBlank, read (not
  consumed) by the present and packed into the GPU snap's hold word (bit 8;
  older snaps derive it from the count). The executable test showed the
  host-side latch made the present of a restored tick differ from live on
  the last held VBlank (hold 0, skip 1), exposing stale trailing FMV
  columns after a rollback load.
- Depth24 scanout staging and trailing-column blanking move from main.cpp
  into gpu_depth24_stage_scanout (gpu.c), so the test renders through the
  same code every CPU depth24 present runs.
- New depth24_present_hold_exec_test: drives real gpu.c through a 15-bit to
  24-bit FMV, GP1(07h) retargets and mode transitions; compares hold, skip,
  upload span and snapshot bytes across windowed/headless/deferred presents,
  rollback replay from every tick and disk loads, and checks staged frames
  never show stale movie pixels while the new movie stays visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:53:21 -07:00
Shokunin 594d365060 GPU: count the depth24 present hold down at guest VBlank
GP1(07h) arms s_d24_present_hold to skip a few Swaps after a depth24
display-band retarget. The hold is in the GPU snap, but it counted down
only at host present: a headless run (and any path that skips the present)
left it stuck, and a rollback snap load zeroed it. In R4's 4-seat rollback
self-test the replayed saves then differed from the live saves of the same
ticks in the GPU section (hold 3 live, 0 replay; #636).

The count now moves at guest VBlank (gpu_vblank_tick), which also latches
whether the following present skips its Swap; the present only consumes
that latch. A rollback snap load keeps the restored count. A disk
savestate load still clears it so the restored VRAM shows at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:19:25 -07:00
Shokunin 83f1468860 In-game menu host: a provider-neutral contract (psx_menu_host.h)
psxrecomp owns the contract between the runtime and any in-game menu
frontend. A provider (PsxMenuProvider: attach, event, is_open, set_open,
draw, detach) is registered by the title with psx_menu_set_provider();
none is registered by default, so a title's UI and pin are unchanged.
The runtime offers every provider the same host (PsxMenuHost): pause
(P1, refused in netplay), video get/apply on PsxHostVideoSettings (P4),
mod switch/option set (P5, via the new psx_mod_feature_enable /
psx_mod_feature_set_option shared with the launcher ABI provider),
reload bindings, exit (quit, back to the frontend loop in-process, or a
quick relaunch with the original arguments), disc/tray slots filled by
the tray policy (psx_menu_set_disc_ops), and optional helpers on the
shared launcher ABI both frontends already speak (game info with the
mod catalogue and lobby, settings get/apply with the handoff's filter
translations).

Policies live here, the same for every provider: the lobby is pumped
during play and a started match ends the local session and starts in
the frontend loop (pending launch, seats and signals kept, pumping
stopped so the match port stays free), the menu is closed and its pause
dropped first; the provider detaches before its GL context goes away at
session teardown and attaches to the next one. --launcher sets
RECOMP_LAUNCHER_PREGAME for frontends that boot into the game;
PSX_HIDDEN_WINDOW is a hidden-window test hook that keeps the frontend.
Nothing here includes or names a frontend's own headers or types.
Tests: psx_menu_host_test, host_overlay_test (relaunch quoting).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:02:57 -07:00
Shokunin 0f09935c23 Live video apply: one texture-filter enum for settings, handoff and menu
Review (#601/#603): the live path and the launcher handoff both collapsed
the texture filter to a boolean (value ? 1 : 0), so neither could carry xBR
(#586) and they could drift from settings.toml. PSX_TEXTURE_FILTER_* is now
the one enum (equal to [video] texture_filtering / gr_set_texture_filter),
and psx_texture_filter_from_ui is the one translation used by the launcher
handoff (seed, UserSettings, live globals) and the in-game apply. Unit-
tested in host_overlay_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:02:57 -07:00
Shokunin 09144c8c31 Live video apply: netplay guard, persist every field, build in Release
Review (#601):
- psx_video_apply_live had no netplay guard. In a match only presentation
  (fullscreen, scanlines, linear present) applies live; dynamic resolution,
  frame generation and the filters are saved and reported with the new
  PSX_VIDEO_RESTART_NETPLAY bit (next session).
- internal_resolution and present_thread are written to UserSettings like
  the other restart-only fields, so the choice survives a restart.
- The live-apply code sat inside #ifndef PSX_NO_DEBUG_TOOLS but is used by
  the launcher path, so Release + launcher did not compile. It now lives
  outside the debug-tools block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:02:57 -07:00
Shokunin ca3994e86d Live video apply (in-game overlay P4)
psx_video_apply_live applies the [video] keys a running game can change
(fullscreen, texture and FMV filter, scanlines, linear present, dynamic
resolution, frame generation) through the existing live setters, saves
them to settings.toml, and reports renderer / internal resolution /
thread changes as restart-only. Nothing calls it without a provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:02:57 -07:00
Shokunin 4cda11d207 Input sink: the netplay admit wait routes events to the overlay first
The admit barrier polls SDL every frame of a netplay match and treated
Esc as "leave the match" before the in-game overlay saw it, so opening
the menu online could end the session (netplay_barrier_escape).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:02:57 -07:00
Shokunin c73b34d578 Overlay input: service the overlay in the low-latency re-drain
Review (#600): with low_latency_input on, the re-drain after the pacer
wait sampled the pad without host_pause_service, so ui_capture was a frame
stale and the overlay's opening chord (Start+Select) could reach the guest
for one frame. The re-drain now services the overlay before the sample.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:32 -07:00
Shokunin 475a3ad6c5 Debug host_mouse: inject a click or wheel step at a window point
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:32 -07:00
Shokunin e21226feb7 Overlay input sink and UI capture gate (in-game overlay P3)
psx_host_set_input_sink: an overlay provider sees host SDL events first,
in the event drain and the host pause loop, and may consume them.
psx_host_set_ui_capture: while on, local game input reads released and
centred (netplay included, so peers receive "nothing pressed") and the
external-input gate stays closed. Debug builds gain host_key, which pushes
a real SDL key event for scripted runs. Inert without a provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:32 -07:00
Shokunin 5c8a0c2d60 Host pause: re-anchor pacing on resume; hold the guest headless too
Review (#598): resuming left the frame pacer's deadline in the past, so it
paid a pause back as up to ~12 unpaced frames. Resume now runs
present_session_reset, an audio cycle resync and a dynamic-resolution hold,
as a savestate load does. host_pause on=1 headless was a no-op that answered
ok; the headless frame path now enters host_pause_loop too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:17 -07:00
Shokunin 138b44a58e Host pause API (in-game overlay P1)
psx_host_pause_push/pop: a nesting, host-owned pause. While it is held the
runtime freezes the guest at the vblank present (as the save-state menu
does), keeps presenting the last frame and pauses audio. Push refuses
during netplay. Nothing pushes unless an overlay provider (or the debug
server's new host_pause command) asks, so behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:33:17 -07:00
Shokunin 43afb3bb16 timing: opt-in batching of the scaled CPU charge
With [timing] guest_cycle_scale active, every one-cycle psx_cyc_charge went
straight to psx_cpu_charge and psx_advance_cycles, bypassing the batch the
scale-1 path uses. [timing] guest_cycle_scale_batch (PSX_GCS_BATCH=0/1)
scales the charge in place, with the same fractional carry and order as
psx_cpu_charge, then batches it like the scale-1 path. It flushes at the same
observation points. Default off.

R4 exactness, frame_fingerprint off vs on, 9000 frames each: headless boot
through intro, title and attract; a 1P Grand Prix race; and a 2P VS race.
cyc, mmio, mc and qc are identical on every frame. sp, wr, pc and ws are
identical at the end. The only differences are single-frame VBlank straddles
of wc/sc (never two frames in a row). 0 dispatch misses.

Speed, release builds, races at 60 Hz:
- M4 (macOS sample): psx_cpu_charge 4.3 % -> 0 % of emulation-thread samples;
  busy 46.0 -> 44.4 % (2 pairs).
- 9800X3D: no measurable change (30.0 vs 30.5 %; one core 44.6 vs 44.2 %).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin f99a318999 quality presets: review fixes (headless, persistence, Linux, renderer table)
1. Detection never runs under --headless / --hidden-window (CI, scripted
   runs): a saved preset applies, else the title's [video]; nothing is
   written. A detection is saved only when the GL probe succeeded; a result
   from the OS name alone is used for that run and the next launch probes
   again (a failed probe could otherwise pin "unknown GPU" -> Ultra).
2. Linux without a GL probe: the classifier now reads the sysfs PCI id
   (Steam Deck parts, Intel integrated vs Arc, NVIDIA).
3. AMD/Intel rules rewritten against real renderer strings: "Radeon RX
   Vega 11 Graphics" and other Vega/"Radeon Graphics"/R5-R7/680M-890M APUs
   are integrated (Low); RX/Pro/W cards and Strix Halo are Ultra; the Intel
   Arc iGPU is Low, Arc A/B cards Ultra. quality_presets_test holds a
   44-row table of Windows, Mesa (incl. Steam Deck LCD/OLED, zink) and macOS
   GL_RENDERER strings, plus probe-failure and headless cases.
5. generated/OpenBIOS.emitter.sha re-stamped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin d5fc43d802 Graphics presets with hardware detection (opt-in per title)
A title may declare [quality.low|medium|high|ultra] tables of [video] keys.
On first launch, and when the hardware fingerprint changes while a preset is
in force, psxrecomp detects the CPU, threads, RAM and GPU (OS facts; a hidden
1x1 GL window reads GL_RENDERER only when a detection is due and the OS did
not name the GPU), picks the matching preset and saves quality_preset,
quality_base, quality_hardware and quality_detected to settings.toml.

- Each preset is the whole game.toml re-parsed with its keys over [video], so
  every [video] key works in a preset with its usual validation.
- A preset in force owns its keys: saved settings.toml values for them are
  ignored. Custom (the player changed a governed setting in the launcher)
  applies the player's values over quality_base and is never overridden;
  new hardware under Custom is only reported.
- Launcher: GameInfo.quality_* (offered mask, detected preset, hardware
  summary, quality_apply / quality_redetect callbacks) and
  Settings.quality_preset/quality_base, behind RECOMP_LAUNCHER_HAS_QUALITY_PRESETS.
- PSX_QUALITY=<preset> for one run, PSX_QUALITY_REDETECT=1.
- Titles without [quality.*] are unchanged.

Rules (quality_tier.c) and the decision are unit-tested in
quality_presets_test; docs/QUALITY_PRESETS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:26 -07:00
Shokunin 7a4a1a004a idle_skip store counters: only audited sites (idle_skip_store_counter_sites)
The detector cannot prove a loop has no later counter-dependent exit, so
the extension now skips only at block-edge PCs the title lists after an
audit. Unlisted sites are never skipped; an empty list disables it. Docs
record R4's v_wait audit as the reference; tests cover unlisted sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Shokunin f2570a8f76 idle_skip store counters: review fixes, exactness from boot, pinning test
- Last-store tracking: memory.c owns the record and writes it only while
  the extension is on (g_psx_track_last_store); main.cpp hands it and the
  host writer (psx_host_write_word) to psx_cycles.c through
  psx_idle_skip_set_store_tracker / _set_host_writer, so psx_cycles.c no
  longer references memory.c (fixes the tests that link one without the
  other).
- Exact from boot: the device distance is now measured from the current
  cycle (devices report it from their last service, up to a few hundred
  cycles behind), and several edges per pass are accepted only together
  with a counter store. Before this, R4's boot diverged at frame 962 (an
  OpenBIOS multi-edge poll and a late VBlank IRQ after a v_wait skip).
- tests/test_idle_skip_store_counter.c: a v_wait trace skips (whole
  passes, counter lowered by the host writer, never to the timeout, lands
  before the event); an extra store, an MMIO read, a decrement of 2, a
  store to MMIO, the option off and no host writer do not.
- generated/OpenBIOS.emitter.sha re-stamped.

R4 frame_fingerprint off vs on, 9000 frames each: boot through intro and
attract, a 1P race and a 2P VS race: cyc, mmio, mc, sp, sc and qc
identical on every frame, 0 dispatch misses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Shokunin 9515cb7dfe idle_skip: elide store-counter wait loops (PsyQ v_wait)
idle_skip only skipped loops with zero stores and a single interrupt-check
edge, so it never fired on PsyQ libetc's v_wait -- the VSync(0)/VSync(n)
spin most PsyQ titles run each frame -- which decrements a stack timeout
word every pass and crosses two block edges.

New opt-in on top of idle_skip, [runtime] idle_skip_store_counters
(PSX_IDLE_SKIP_EXT=0/1 for one run):
- an iteration may span up to 8 block edges; the first edge is the anchor
  and the edge count must be stable;
- one word store per iteration to the same RAM/scratchpad address whose
  value drops by exactly 1 (a register holding it may drop with it).
Skips lower the counter by k (never to <= 1) and, for multi-edge loops, land
on the anchor strictly before the next device event so an IRQ is taken at
the same edge as without the skip. memory.c records the last word store's
address and value for the detector.

R4 race, 3600 frames from one savestate, held input: cyc, mmio, mc, sp, sc
and qc identical every frame; wc differs only by the elided counter stores.
Release build on an Apple M4: emulation-thread busy 47.5% -> 34.9% at 60 Hz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:31:25 -07:00
Matthew Stanley 1473c66772 Merge validated renderer fixes into generic post-effect pipeline 2026-10-13 10:18:03 -07:00
Shokunin abbae48947 Dithering on by default with explicit per-primitive eligibility
Maintainer review of #584: PS1 dithering is hardware behaviour, so it is
now on by default whenever the guest sets GP0(E1h) bit 9 ([video]
dithering = "on"; "scaled" stays opt-in, "off" is the old true-colour
output).

Eligibility is decided per primitive from its command and carried in the
vertex instead of being inferred from the colour:
- Gouraud polygons and shaded lines: geometry vertex a_col.a + 2*131072
  (decoded in GEO_VS, v_dith);
- texture-modulated polygons: a_semi + 8 (v_dith in TEX_VS);
- flat polygons/lines, raw-texture polygons and every rectangle: never.
This fixes the two reproduced bugs (Gouraud with 5-bit-exact colours was
skipped; textured rectangles were dithered). Bit changes no longer flush:
eligibility is fixed when the primitive is queued; mode changes still drain.

gl_dithering_test covers each case (Gouraud incl. equal colours, shaded
line, modulated and neutral-modulated textured polys dithered; flat tri,
flat rect, flat line, raw poly, modulated and raw textured rects never;
bit clear and off unchanged), plus scaled 4x == 1x, render thread, PGXP
depth, native-wide and the accurate-blending basis. OpenBIOS stamp
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:18:29 -07:00
Shokunin 023af6d9d4 GL: opt-in PS1 dithering ([video] dithering = off|on|scaled)
The OpenGL renderer has always drawn shaded and texture-modulated pixels
at 8 bits per channel with no dither pattern (true colour). That stays the
default. 'on' applies the PS1 4x4 offset matrix and 5-bit truncation per
internal-resolution pixel; 'scaled' puts the pattern on the native grid,
so it reads like the console at any scale and the native VRAM is the same
at every internal resolution.

GP0(E1h) bit 9 reaches the backend through a new optional facade hook
(gr_set_dither) recorded for the render thread and Smooth motion redraws.
With the mode off nothing flushes and no uniform changes. Flat, raw and
other 5-bit-exact pixels are never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:18:29 -07:00
Shokunin 18e10693c4 post effects: MinGW build fix, generated frames reuse bloom's glow, no glGet in marking
- config: [video] post_shader is a std::filesystem::path (like memcard_dir);
  the frontend turns it into UTF-8 bytes for the GL API. Assigning the path
  to a std::string did not compile on MinGW.
- .pfx `generated reuse`: a Smooth motion generated frame keeps that pass's
  target from the last real frame. The bloom preset marks its bright and
  blur passes so; generated frames run only the masked composite (over their
  own picture and mask). bloom debug reports real/generated pass times and
  reused passes.
- Mask marking no longer queries GL state per batch: each call site puts
  back the blend and mask-bit stencil it set (no driver round trips on the
  render thread).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin 74dadc6004 GL: one post-effect pipeline; bloom and FXAA as built-in chains, user chains via [video] post_shader
Post effects now run as chains of full-screen fragment passes on one
executor (gl_postfx.inc), as asked in review. A chain is a small .pfx preset
(postfx_chain.c, GL-free): passes with a shader (a file next to the preset or
builtin:<name>), inputs (source, the optional primitive-derived mask,
previous or a named earlier pass), a scale, format, filter and uniforms.
The executor owns target allocation and lifetime, program build/cache,
uniform binding (u_rcp, u_size, u_org) and GL state save/restore.

Stages, in order, each from a fresh copy of the image: the built-in "bloom"
chain ([video] bloom), a user chain ([video] post_shader / PSX_POST_SHADER,
elective, off by default), and the built-in "fxaa"/"fxaa_hq" chain
([video] antialiasing_mode). FXAA folds in as a one-pass chain and its
output is byte-identical to the former fixed pass; it still runs on real
frames only. Generated (Smooth motion) frames run bloom and the user chain.

The "may glow" mask is an optional pipeline input: draws keep stencil bit 1
only while an active chain reads `mask`, and the window-sized mask texture is
built once per present for the chains that use it.

Tests: postfx_chain_test (format, built-ins, errors, file import, no GL);
gl_bloom_test gains an imported two-pass chain (byte-exact invert), the mask
input (HUD unlit, 3D lit), a bad preset, a pass that does not compile, and GL
state restored with bloom + a mask chain + FXAA on, all on the caller thread
and the render thread. docs/POST_EFFECTS.md documents the format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin e730e03a8a GL: opt-in bloom ([video] bloom), sourced from 3D and additive primitives only
[video] bloom = 0.0 (off, default) .. 2.0 adds a glow around lights on the
OpenGL backend. PSX_BLOOM overrides it for one run; {"cmd":"bloom"} reads or
changes strength, threshold and a debug view live. It is a plain [video] key,
so [quality.*] presets can switch it off on low-end machines.

The HUD never glows: with bloom on every textured and untextured draw is
followed by two stencil-only passes of the same vertices that keep a
"may glow" mark in stencil bit 1 (bit 0 stays the mask-bit mirror). Opaque
primitives clear it under their coverage, then PGXP-proven 3D primitives and
additive semi-transparent texels/primitives (modes 1 and 3) set it, painter
ordered like the colour; 3D and non-3D primitives never share a batch while
bloom is on. Semi-transparent primitives leave the mark alone and the PGXP
depth-clear pass (colour writes off) marks nothing. Without PGXP geometry only
additive primitives glow.

The pass runs on the composed game image before AA and the OSD: the presented
surface's marks are blitted into a quarter-resolution stencil, a soft-knee
bright pass runs under it, three separable Gaussian iterations spread it and a
screen blend adds it back. The marks follow the picture: native-wide centre
copies and Smooth motion surfaces (redraw and reprojection) copy stencil with
colour, and generated frames get the same pass. Off: nothing runs, no state
changes, and the composed frame is byte-identical to master.

test_gl_bloom (gl_bloom_test): off is byte-identical before/after bloom and
with --dump against master; on, a bright 2D panel and a panel over a lit 3D
polygon are byte-identical to off, an additive and a 3D polygon glow; guest
VRAM is unchanged; on this thread and on the render thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:15:46 -07:00
Shokunin f6c86b01cd Game Mode: keep only the gamescope signals and SteamDeck=1
SteamGamepadUI=1 is also set when Steam launches from Big Picture on an
ordinary desktop, which is broader than Steam Deck / gamescope. Detection is
now XDG_CURRENT_DESKTOP=gamescope, GAMESCOPE_WAYLAND_DISPLAY or SteamDeck=1
(plus --game-mode / PSX_GAME_MODE); game_mode_test and docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:51:59 -07:00
Shokunin 477bb188b4 Game Mode: Steam Deck / gamescope boots straight into the game, fullscreen
Detected from a gamescope session (XDG_CURRENT_DESKTOP, GAMESCOPE_WAYLAND_DISPLAY)
or Steam's gamepad UI (SteamGamepadUI=1); --game-mode / --no-game-mode and
PSX_GAME_MODE override. When on and a disc is set up, the launcher is
skipped and the game opens borderless fullscreen for that run (the saved
Fullscreen choice is untouched); the first run still shows the launcher so
the disc can be picked. Desktop sessions, --headless and --hidden-window are
unchanged. game_mode_test covers detection and precedence; docs/GAME_MODE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:51:59 -07:00
Shokunin 47fb2af915 Launcher handoff: keep the texture filter enum (xBR no longer becomes bilinear)
The seed path collapsed ls.texture_filter to a boolean, so a launcher's
xBR (3) was saved and applied as bilinear, and the rematch paths passed
the raw value through unchecked. psx_texture_filter_from_ui is now the
one translation (in-range values pass, others are nearest). Test:
video_texture_filter_test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:06:00 -07:00
Shokunin 2d47dabfa2 JogCon FFB: player setting, off by default, live-applicable
Owner decision: wheel force feedback is an optional player setting, off by
default. settings.toml [controller] jogcon_force_feedback (UserSettings,
load + save) overrides the title's game.toml
experimental_jogcon_force_feedback default. psx_jogcon_ffb_set_player_enabled
applies it live (off -> every wheel neutral now); the launcher's boot and
lobby soft-return exits seed and read RecompLauncherCSettings
.jogcon_force_feedback (RECOMP_LAUNCHER_HAS_JOGCON_FORCE_FEEDBACK,
RetroPortingToolKit/recomp-ui#95) and persist it. Tests:
jogcon_ffb_settings_test (default off, on, explicit off, round-trip);
jogcon_ffb_test (setting off: no haptic calls; toggled on: the hold drives;
off live: neutral).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 01:02:17 -07:00
Shokunin 05931590a3 JogCon FFB: experimental opt-in, neutral on pause / disconnect / end
Review (mstan): the title key is now experimental_jogcon_force_feedback(
_invert) and the runtime logs it as EXPERIMENTAL. Force feedback goes through
a pure driver (psx_jogcon_ffb.h) and an SDL haptic backend
(psx_jogcon_ffb_sdl.cpp): every force has a 120 ms lifetime renewed each
VBlank, so a pause, stall or crash goes slack by itself, and the Rewind and
save-state pause loops, disconnect (close_player) and session end
(close_controller) stop it explicitly. Tests: jogcon_ffb_test drives the
driver against a recording backend (lifetime, renew, stop once, neutral,
idempotence, disable mid-force); jogcon_ffb_sdl_test runs the SDL backend
against an SDL3 virtual wheel. SDL has no virtual haptic device, so it checks
that such a device is not driven, never falls back to rumble and closes
cleanly. Also fixes the SDL3 haptic subsystem init check (psx_sdl.h returns
0 on success).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:44:05 -07:00
Shokunin efe685cf1e JogCon force feedback on host wheels (SDL haptics, opt-in)
The emulated JogCon kept its motor command as device state only. With
game.toml [controller] jogcon_force_feedback = true, a presented JogCon's
motor byte (command nibble + strength) now drives a host wheel's SDL haptics:
commands 1 / 2 a constant force signed by direction and scaled by strength,
any other non-stop command a centring spring. Only pads whose haptic device
supports constant or spring effects are driven, so gamepads are untouched.
jogcon_force_feedback_invert flips the turn direction (not checked on JogCon
hardware). SIO keeps the strength nibble beside the command
(sio_get_pad_jogcon_motor). PSX_RUMBLE_TRACE logs the commands, headless too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:40:55 -07:00
Alex Vanderveen c47f7391e8 fix netplay 2026-10-11 16:16:33 -04:00