Files
psxrecomp/docs/QUALITY_PRESETS.md
T
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

193 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Graphics presets and hardware detection
A title can offer named graphics presets, Low, Medium, High and Ultra, the way
PC games do. On first launch psxrecomp detects the machine and picks one, saves
the pick, and shows it in Settings → Display → **Graphics preset** together with
what it detected and a **Re-detect** button. Change any setting a preset sets and
the preset becomes **Custom**. psxrecomp never overrides a Custom setup on its own.
Dynamic resolution stays on in every preset as the safety net under the preset.
A title with no `[quality.*]` tables gets none of this, and nothing changes.
## Declaring presets (game.toml)
```toml
[video] # the shipped look; usually Ultra
supersample = 1.5
frame_generation = true
dynamic_resolution_min = "display"
[quality.ultra] # empty: exactly [video]
[quality.high]
supersample = 1.25
[quality.medium]
supersample = 1.0
dynamic_resolution_min = "720p"
[quality.low]
supersample = 1.0
dynamic_resolution_min = "native"
frame_generation = false
pgxp_depth_buffer = false # PGXP itself stays on (a mod switch)
pgxp_color_correction = false
pgxp_seam = "off"
```
Each `[quality.<name>]` table holds `[video]` keys from the supported set
below. The preset is the whole game.toml re-parsed with those keys laid over
`[video]`, so each key gets its usual `[video]` validation. The names are
`low`, `medium`, `high` and `ultra`. Any subset may be offered. Any other name,
a table value, an invalid value, or a key outside the supported set is a load
error. The keys a title's presets set, taken together, are the keys presets
**govern**.
### Supported keys
Every key in the set is fully owned by the preset system. A preset in force
masks the player's saved value. A one-run override, or an edit that touches no
governed key, keeps the value on disk. A launcher row or live in-game change
to a governed key is a governed edit and makes the state Custom. Picking a
preset applies the key at once on the boot launcher, and at the next launch
from the soft-return launcher.
| Kind | Keys |
|---|---|
| Player-editable (settings.toml form, launcher row) | `texture_filtering`, `fmv_filter`, `antialiasing`, `dynamic_resolution`, `dynamic_resolution_min`, `frame_generation`, `render_thread`, `present_thread`, `geometry_correction`, `perspective_texturing`, `frame_interpolation`, `frame_interpolation_fps` |
| Preset-only (no settings.toml form, no row) | `supersample`, `texture_lod`, `anisotropic_filtering`, `fmv_chroma_smoothing`, `bloom`, `dithering`, `pgxp_depth_buffer`, `pgxp_color_correction`, `pgxp_seam` |
Other `[video]` keys (`vsync`, `renderer`, `scanlines`, `internal_resolution`,
`supersampling`, ...) are rejected in a preset: they stay the player's.
`PSXRecompV4::quality_key_supported` is the list; extending it means adding
masking, copying, edit classification and apply-on-select for the key.
## Detection
`quality_probe.c` reads OS facts only on every launch: the CPU brand, logical
thread count and RAM, the GPU name on Windows (primary display device) and
macOS (Apple silicon names the GPU after the chip), the GPU's PCI id on Linux,
and Steam Deck DMI (`Valve` / `Jupiter` or `Galileo`) or `SteamDeck=1`. These
facts hash to the **hardware fingerprint**. When a detection is due, a hidden
1×1 OpenGL window reads `GL_RENDERER`: the GPU OpenGL actually renders on,
which is not always the adapter the OS names (a desktop whose display hangs off
the CPU's iGPU while a discrete card sits idle, or a hybrid laptop). The OS
name is the fallback when the probe fails, and the probe is skipped under the
`dummy`/`offscreen` SDL video drivers. The context-creation log line names the
renderer too.
`quality_tier.c` maps the facts to a tier. It is a pure function, unit-tested in
`recompiler/tests/quality_presets_test.cpp` against a table of real
`GL_RENDERER` strings from Windows drivers, Mesa and macOS.
**What it is, and what it is not.** Autodetect is a static heuristic over the
GPU name, the CPU thread count and the memory size. It measures nothing:
there is no benchmark, and a name says nothing about clocks, thermals or the
driver. It is a first-launch starting point, and the dynamic systems
(dynamic resolution down to the title's floor, Smooth motion's in-between
frames made from surplus only, the adaptive aspect) are what actually keep
the frame rate.
| Class | Detected |
| --- | --- |
| Software GL, Steam Deck (Van Gogh), mobile GPUs, every integrated GPU up to the Radeon 890M / Iris Xe / Arc iGPU class, Apple M1 and M2 base chips, GeForce MX/GT | Low |
| A GPU the rules do not recognise (including Linux with only an AMD PCI id, and unnamed nouveau chips) | Medium |
| Recognised capable GPUs: discrete RX/RTX/GTX/Arc A/B cards, Apple M3 and later, any Pro/Max/Ultra chip, Strix Halo (8050S/8060S) | Ultra |
Fewer than 5 CPU threads, or under 6 GB of memory, also gives Low. High is a
manual choice. If the detected tier is not offered, the highest offered tier
below it is used, or failing that the lowest one above it.
**Why unknown is Medium.** Nothing about an unrecognised GPU is known, so the
fallback must not assume Ultra's extra work (supersampling above the display,
and whatever a title adds there, such as R4's mipmaps, 16x aniso and bloom).
Low would also be wrong for what is usually new hardware: it gives up
Smooth motion and the PGXP extras on machines that can almost always run
them. Medium keeps the display's own resolution with dynamic resolution
under it, which is the safe middle. A player can pick any preset at any
time.
On Linux, when the GL probe fails, the sysfs PCI id is used instead (Steam
Deck parts, Intel integrated vs Arc, NVIDIA).
## When detection runs, and what is saved
settings.toml `[video]` keeps the following:
| Key | Meaning |
| --- | --- |
| `quality_preset` | `low`..`ultra`, or `custom` |
| `quality_base` | the preset a `custom` started from (its values for keys with no launcher row, such as `supersample`) |
| `quality_hardware` | the fingerprint the last detection ran on |
| `quality_detected` | what that detection picked |
Detection runs on first launch (no `quality_preset`), whenever the fingerprint
changes while a preset (not Custom) is in force, and on **Re-detect**. It never
runs under `--headless` or `--hidden-window` (CI and scripted runs): a saved
preset applies there, or else the title's `[video]` block, and 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 new
machine under Custom is only reported:
psxrecomp: new hardware since your graphics settings were made; your Custom settings are kept
While a preset is in force it owns its keys. The player's saved settings.toml
values for those keys are ignored, so a launcher save of the preset's own values
cannot pin them. Under Custom the player's saved values apply on top of
`quality_base`. Keys a preset does not govern always stay the player's.
`PSX_QUALITY=low|medium|high|ultra` picks a preset for one run and is never
saved. `PSX_QUALITY_REDETECT=1` forces a detection.
### Who owns a saved key: one transaction
Every settings.toml save that can carry governed keys goes through
`psxq::settle_save` (`quality_presets.cpp`): the boot launcher (Play and Quit),
the soft-return launcher, and the in-game menu's live video settings. The
frontend only reports what the player did (below); the host decides what is
written, against settings.toml as it is on disk at that moment:
| This run | Player did | Saved |
|---|---|---|
| `PSX_QUALITY` override | anything | the governed keys and `quality_*` keys exactly as on disk: the previous preset, or Custom and its values, survive. A governed edit applies to this run only. |
| a preset in force | picked another preset | the picked preset; its keys stay as on disk (it owns them) |
| a preset in force | changed a governed value (launcher row, or a live in-game change) | Custom, from the preset in force, with the edited values; the next launch keeps them |
| Custom | anything | Custom, with the edited values |
| a preset in force | changed only keys no preset governs | the preset, unchanged; those keys are saved |
This holds when a launcher builds its save from an empty seed (the boot
launcher does): governed values the player did not choose are never written.
`quality_presets_test` covers each row through a real settings.toml round
trip, including the next launch's masking.
## Launcher
`RecompLauncherCGameInfo.quality_*` gives the launcher the offered presets, the
detected preset, a hardware summary (for example "Apple M1 · 8 threads · 8 GB"),
and two callbacks:
- `quality_apply` fills the Settings rows a preset sets. The host runs the
preset's values through the same conversions it uses to seed the launcher.
- `quality_redetect` probes again, GL included, and returns the preset.
`Settings.quality_preset` is 1–4 for a preset and 5 for Custom. The launcher
snapshots the rows after a preset is applied. As soon as a tracked row differs
from the snapshot, it sets Custom and keeps the preset it came from in
`quality_base`. recomp-ui and recomp-launcher report the same way. The
frontends track every quality row, so the host classifies the report itself
(`psxq::launcher_custom_edit`): it fills a copy of the rows from the reported
base preset and counts only rows of keys this title governs. A Custom flag
raised by an ungoverned row (for example Antialiasing when no preset sets it)
keeps the preset, or records the pick it followed. The host then saves the
state through the transaction above. (The launcher may still label such an
edit Custom until it is reopened.) A newly picked
preset's keys that have no row, such as `supersample` and the PGXP extras,
apply at once on the boot launcher and at the next launch from the soft-return
launcher.
The in-game menu's live video settings (`psx_video_apply_live`) have no preset
row. The host compares the governed values the menu changes (texture filter,
FMV filter, smooth scaling, dynamic resolution, Smooth motion, render and
present threads, internal resolution) with the ones in force; a change to any
of them is a governed edit and makes the state Custom.