mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-27 18:52:11 -04:00
8be777f1f1
- [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>
193 lines
10 KiB
Markdown
193 lines
10 KiB
Markdown
# 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.
|