Compare commits

...

130 commits

Author SHA1 Message Date
Stenzek
b798d8f1aa
Settings: Add option for enabling GPU-based validation 2025-03-30 23:20:39 +10:00
Stenzek
049ec4af90
CI/Linux: Bump discord-rpc/lunasvg commits 2025-03-30 22:27:59 +10:00
Stenzek
162d742cc0
CDROM: Remove hack for Fighting Force
Instead properly handle the one-sector-delay that occurs when autopause
detects the start of a new track.
2025-03-30 22:00:21 +10:00
Stenzek
8c1f75ab8c
GameDatabase: Add overrides for maximum CD speedup cycles 2025-03-30 22:00:21 +10:00
Stenzek
d2c09dfaff
Settings: Split read and seek cycles for maximum CD speedup 2025-03-30 22:00:21 +10:00
Stenzek
3292833fb8
FullscreenUI: Add option to turn off animations 2025-03-30 15:58:48 +10:00
Stenzek
04c5a9f1b2
FullscreenUI: Improve leaderboard text contrast 2025-03-30 15:43:15 +10:00
Stenzek
c5cc94c4b7
FullscreenUI: Fix hotkey categories duplicating 2025-03-30 15:22:46 +10:00
Stenzek
64513395f3
FullscreenUI: Add fade transition to most screen changes 2025-03-30 15:22:46 +10:00
Stenzek
e6e6313219
FullscreenUI: Add offscreen-based screen fade 2025-03-30 15:22:46 +10:00
Stenzek
4e2872f248
FullscreenUI: Fix consecutive choice dialogs 2025-03-30 14:45:26 +10:00
Stenzek
2d31bf5685
FullscreenUI: Convert input binding into a PopupDialog 2025-03-30 14:45:26 +10:00
Stenzek
9bcd738f5c
FullscreenUI: Make save state selector a main window
More reliable, saves messing around with popups.
2025-03-30 14:45:26 +10:00
Stenzek
3bf5ded0d2
FullscreenUI: Fix various issues
- Pause menu losing key focus the first time it's opened.
- Pause menu being unopenable after close when paused.
- Key nav not being enabled after closing game via main UI.
2025-03-30 11:47:31 +10:00
Stenzek
a3e840526a
FullscreenUI: Fix multi-disc selection with duplicate filenames 2025-03-29 21:46:56 +10:00
Stenzek
54c42f9c27
Achievements: Consider all-hidden-leaderboards as no leaderboards
Stops the menu option being available and showing 0 leaderboards.
2025-03-29 20:06:09 +10:00
Stenzek
b4ae19411b
Achievements: Show unconfirmed unlocks in pause menu 2025-03-29 20:06:09 +10:00
Stenzek
37230107f4
FullscreenUI: Run idle while notifications/toasts are onscreen 2025-03-29 20:06:09 +10:00
Stenzek
730fa67952
FullscreenUI: Darken titled toast text 2025-03-29 20:06:09 +10:00
Stenzek
3cf2f41723
FullscreenUI: Fix crash changing renderer 2025-03-29 20:06:09 +10:00
Stenzek
ba3c693717
Qt: Fix double memcard warning on shutdown 2025-03-29 18:01:10 +10:00
Stenzek
0687e59bda
FullscreenUI: Rewrite popup dialog handling
Add animations, fix background dimming.
2025-03-29 17:57:37 +10:00
Stenzek
576658b6eb
Settings: Max speedup cycles should be at least 1 2025-03-28 21:23:32 +10:00
Stenzek
c7504e7016
dep/fmt: Bump to v11.1.4 2025-03-28 20:54:04 +10:00
Stenzek
e4a028834a
FullscreenUI: Fix keynav default in open-in-game-list mode 2025-03-28 20:44:54 +10:00
Stenzek
f36062a94e
FullscreenUI: Fix duplicate item in advanced settings 2025-03-28 20:40:52 +10:00
Stenzek
9ef7f54f19
FullscreenUI: Enable all windows to nav-wrap
i.e. when pressing down at the end, it wraps to the top.
2025-03-28 20:38:31 +10:00
Stenzek
003518c234
FullscreenUI: Fix nav wrapping if first item is disabled 2025-03-28 20:12:41 +10:00
Stenzek
6f2225fb61
FullscreenUI: Remove redundant ActiveButton() and friends 2025-03-28 19:45:52 +10:00
Stenzek
f80a77fa27
FullscreenUI: Fix popup item border going outside of window 2025-03-28 19:27:24 +10:00
Stenzek
ea3774cece
FullscreenUI: Fix setting BIOS to Auto Detect 2025-03-28 18:54:14 +10:00
Stenzek
58bd87666e
Qt: Fix crash when FSUI open and game list activated 2025-03-28 17:25:53 +10:00
Stenzek
fbadacae78
CDROM: Fudge track autopause hold position
Fudge the hold position by 2 sectors to reduce the number of GetlocP's
that will return a MSF in the old track.

Works around the music hang in Fighting Force.
2025-03-28 17:24:39 +10:00
Stenzek
ceef7af1da
GameList: Fix crash if cache fails to open 2025-03-28 17:07:13 +10:00
Stenzek
ece18d10c5
PCDrv: Allow opening by absolute path 2025-03-24 18:59:15 +10:00
Stenzek
ae9023ddf3
CI: Build armhf AppImages 2025-03-23 23:41:19 +10:00
Stenzek
3f0abccb59
CI: Don't use zlib-ng on cross-compiled AppImages
Seems to be problematic on armhf, and I can't be arsed debugging it.
2025-03-23 23:41:00 +10:00
Stenzek
104c82c2c3
Mini: Fix right button forwarding 2025-03-23 23:39:26 +10:00
Stenzek
2cb6ce635f
Mini: Fix non-first game list refresh 2025-03-23 23:39:15 +10:00
Stenzek
4ce001aef3
GameDatabase: Add DisableFullTrueColor trait 2025-03-23 17:08:25 +10:00
Stenzek
1fca8ae6bf
FullscreenUI: Fix start-in-game-list mode using grid 2025-03-23 17:02:52 +10:00
Stenzek
3ffd20b833
FullscreenUI: Display game list attributes in darker colour 2025-03-23 16:59:23 +10:00
Stenzek
9411e40c20
FullscreenUI: Fix assert when booting+loading state 2025-03-23 16:43:57 +10:00
Stenzek
03d7af2efc
FullscreenUI: Add border rounding to menu items 2025-03-23 16:42:24 +10:00
Stenzek
9020959511
Qt: Add game list background function 2025-03-23 15:58:56 +10:00
Stenzek
98d1c71981
GameDB: Force full true color for Bust a Move 2
Not strictly accurate, but looks better.
2025-03-22 18:32:31 +10:00
Stenzek
741e971681
GPU/HW: Add 'True Color (Full)' dithering mode
This is equivalent to the old 'True Color' mode.

The new 'True Color' mode truncates flat-shaded sprites/polygons
to 16-bit color before drawing. Doing so fixes:

- Menu background in Breath of Fire IV.
- Loading background in JumpStart Wildlife Safari - Field Trip.
- and other similar games.
2025-03-22 18:32:31 +10:00
Stenzek
2d477f80b0
Qt: Add tooltips to setup wizard graphics settings
Same strings as the main graphics settings page, so translation should
be just a matter of accepting them as-is.
2025-03-22 18:32:31 +10:00
Stenzek
2311c8d287
Settings: Convert dithering/true color options to a dropdown
Half of them are mutually exclusive, so this simplifies things.
Fewer options = better, right?
2025-03-22 18:20:51 +10:00
Stenzek
32b09193d1
Qt: Move force video timing to console settings
Since it's related to the region, it makes sense to be in here.
And it frees up precious vertical space.
2025-03-22 18:20:51 +10:00
Stenzek
4afa41b927
Qt: Move async readahead sectors to advanced settings 2025-03-22 18:20:51 +10:00
Stenzek
3ab3e4be06
ImGuiManager: Tweak OSD message rounding 2025-03-22 18:20:51 +10:00
zkdpower
f4e93df1b1
Update the Simple-Chinese translation to latest. (#3395) 2025-03-22 17:10:12 +10:00
Stenzek
115ba4433c
CPU/Interpreter: Emulate lo/hi read stall after mult/div
Still need to do the recompiler.
2025-03-22 01:59:00 +10:00
Stenzek
74064af730
GameDB: Set disable scaled interlacing on various games
Fixes intro fade/transitions in Final Fantasy VIII.
Fixes menu corruption in Destruction Derby Raw.
2025-03-22 01:19:01 +10:00
Stenzek
62d7a73381
GPU/HW: Add 'Scaled Interlacing' option
Disabling this option causes rendering to skip N lines instead of 1
line, where N is the internal resolution multiplier.
2025-03-22 01:19:01 +10:00
Stenzek
d176109d3b
GPU/HW: Fix incorrect interlaced VRAM extract offset
Fixes exascerbated combing when upscaling.
2025-03-22 01:11:24 +10:00
Stenzek
6eddc0b982
GPUDevice: Don't allow copy-resize between different formats
That's a spec violation.
2025-03-22 01:11:23 +10:00
Stenzek
346f0f945d
GameDatabase: Fix incorrect multitap key parsing 2025-03-21 19:11:50 +10:00
Stenzek
de6a8fba51
BIOS: Set priority of v4.1 E to 10
It doesn't have the additional region check.
2025-03-21 19:11:47 +10:00
Stenzek
5c41a48e39
GPU/HW: Fix crash when changing downsampling factor 2025-03-20 21:19:08 +10:00
zkdpower
19c03aff4d
Update the Simple-Chinese translation to latest. (#3394) 2025-03-20 21:17:47 +10:00
Anderson Cardoso
fffa44911c
Atualização Português do Brasil (#3393)
Atualizado para a última versão.
2025-03-20 21:17:39 +10:00
JukePlz
10fea83b4a
GameDB: Intelligent Qube doesn't support analog controller (#3392)
Same for the demos.
2025-03-20 21:17:20 +10:00
Daniel Nylander
eea4159756
Updated Swedish translation (#3391) 2025-03-20 21:17:08 +10:00
KamFretoZ
e5cec05633
Qt/BPM: Add Green Giant Theme (#3390)
WHAT ARE YE DOING IN MY SWAMP???
2025-03-20 21:16:45 +10:00
Stenzek
a1d69982f3
CI: Update AppStream metainfo 2025-03-15 18:49:58 +10:00
Stenzek
e3cb3d029b
FullscreenUI: Fix system not unpausing after HC disable popup 2025-03-15 16:27:43 +10:00
Stenzek
27fc7de07a
FullscreenUI: Add translucency/shadow to achievement notifications 2025-03-15 16:27:43 +10:00
Stenzek
a1e5bb7bc0
FullscreenUI: Display legend for achievement status icons 2025-03-15 15:58:39 +10:00
Stenzek
61de096d26
FullscreenUI: Add status text (left side of footer) 2025-03-15 15:58:22 +10:00
Stenzek
4f00a7c7c1
FullscreenUI: Add rounding to achievements progress bars 2025-03-15 15:44:23 +10:00
Stenzek
3420af829c
FullscreenUI: Display unlock rarity in achievements view 2025-03-15 15:41:30 +10:00
Stenzek
830842891e
GameList: Rename max value enum for consistency 2025-03-15 15:03:42 +10:00
Stenzek
dd885cfe2f
GameList: Cache invalid entries
Saves repeatedly scanning them every time the application starts.
2025-03-15 15:02:56 +10:00
Stenzek
8a0400ad2c
GameList: Don't try to scan gpudump files 2025-03-15 14:46:19 +10:00
Stenzek
bcd4b918dc
FullscreenUI: Fix black screen on renderer change 2025-03-15 14:39:22 +10:00
Stenzek
c82c007a75
FullscreenUI: Add settings reset option to settings 2025-03-15 14:33:51 +10:00
Stenzek
0a470067f5
FullscreenUI: Move appearance up in settings order 2025-03-15 14:07:35 +10:00
Stenzek
9734066514
FullscreenUI: Add StaticGray background 2025-03-15 14:05:09 +10:00
Stenzek
2a86faa51e
FullscreenUI: Replace landing icons with colourable icons 2025-03-15 13:56:29 +10:00
Stenzek
4bac7cb79e
FullscreenUI: Fix some dialogs not being cancellable
i.e. exitable with right mouse click/B button.
2025-03-15 02:18:20 +10:00
KamFretoZ
db3b689166
FullscreenUI: Add Dark Ruby theme (#3389) 2025-03-15 02:17:54 +10:00
Stenzek
1189f53840
FullscreenUI: Add 'Automatic' theme that syncs with Qt 2025-03-15 00:42:02 +10:00
Stenzek
6e27b8ab91
FullscreenUI: Add option to start/open to game list 2025-03-14 23:57:14 +10:00
Stenzek
3d0f038697
FullscreenUI: Move game list settings to main tab
And add resume to game list/grid.
2025-03-14 23:53:50 +10:00
Stenzek
6c867859bf
FullscreenUI: Apply theme to loading screen 2025-03-14 23:07:14 +10:00
Stenzek
f11c31b41c
ImGuiOverlays: Apply theme to save state selector 2025-03-14 22:58:02 +10:00
Stenzek
cffafad2ca
ImGuiManager: Use fullscreen UI theme for OSD 2025-03-14 22:36:14 +10:00
Stenzek
b6fa2cecd3
FullscreenUI: Fix shadowed text alpha again 2025-03-14 22:36:14 +10:00
Stenzek
9c971825a9
FullscreenUI: Apply theme to ingame achievement overlays 2025-03-14 21:22:17 +10:00
Stenzek
c57689cc90
FullscreenUI: Inherit colour alpha for shadowed text 2025-03-14 21:21:56 +10:00
Stenzek
511663198e
Qt: Update glyph ranges 2025-03-14 20:52:43 +10:00
zkdpower
531b3e103a
Update the Simple-Chinese translation to latest. (#3388) 2025-03-14 20:52:19 +10:00
Stenzek
e422afdec1
FullscreenUI: Improve achievements pause menu overlays
- Add most recent unlock/nearest completion.
- Make it look nicer and better fit with the rest of the interface.
2025-03-14 20:50:20 +10:00
Stenzek
ba3295930c
VulkanDevice: Allow 0x0 window size
Fixes minimizing in render-to-separate-window mode.
2025-03-12 23:54:25 +10:00
KamFretoZ
4fc49ee0ca
FullscreenUI: Tweak the highlight background color (#3387) 2025-03-12 23:54:15 +10:00
Stenzek
f6296ceb09
Qt: Increase setup wizard dialog height
Needs it on Linux otherwise graphics combos are shrunk.
2025-03-12 01:46:10 +10:00
Stenzek
9a9ed52b70
Qt: Fix vertical alignment in patches page 2025-03-12 01:46:07 +10:00
Stenzek
44af55a77f
FullscreenUI: Differentiate title/subtitle colours
Makes it easier to read.
2025-03-12 01:46:04 +10:00
Stenzek
72a0ba1e8e
FullscreenUI: Fix pause menu light theme shadow colour 2025-03-12 00:19:32 +10:00
Stenzek
aeadd4c280
FullscreenUI: Move game info to top of pause menu 2025-03-11 23:19:00 +10:00
Stenzek
d6ab840e4b
FullscreenUI: Use RetroAchievements game icon as fallback game image 2025-03-11 22:38:43 +10:00
Stenzek
492a55ee8b
FullscreenUI: Add icons to achievement list categories 2025-03-11 22:27:08 +10:00
Stenzek
541af8d5de
GDBServer: Stub out thread commands 2025-03-11 22:07:18 +10:00
Stenzek
7bae23d79d
GDBServer: Implement memory breakpoints 2025-03-11 21:50:26 +10:00
Stenzek
a0c06f8d9c
GDBServer: Implement 's' command (single step) 2025-03-11 21:32:03 +10:00
Stenzek
814263b442
GDBServer: Mostly rewrite handlers and fix undefined behaviour
Remove all heap allocations.
Remove copies.
2025-03-11 21:29:29 +10:00
Stenzek
2f5855a7a4
GDBServer: Get rid of <map> usage 2025-03-11 20:37:37 +10:00
Stenzek
cd694c01d8
GDBServer: Improve ack handling
Treat acks as complete packets, that way it doesn't spam an error
when only an ack is sent.
2025-03-11 20:30:47 +10:00
Stenzek
c717f547cf
Deps: Update to SDL3 3.2.8 2025-03-10 21:54:45 +10:00
Stenzek
8bffd9725e
Qt: Lock system when automatic updater shows
Prevents messyness when launching in fullscreen+big picture.
2025-03-10 21:54:45 +10:00
Stenzek
e25f287d60
Qt: Avoid stuck in temporary render-to-separate mode 2025-03-10 21:54:45 +10:00
Stenzek
9f0bbccd50
Achievements: Fix progress bar text alignment 2025-03-10 21:54:45 +10:00
Víctor "IlDucci
29f19d0ab6
Spanish (Spain) translation update (#3386)
- Adding new strings.
2025-03-09 16:10:49 +10:00
Stenzek
58dc7562a3
GPU/HW: Add in-pass clear depth pipeline
Instead of clearing the entire buffer, we only need to wipe out the
current drawing area. Saves a decent chunk of memory bandwidth in games
that end up spamming clears.
2025-03-07 21:33:06 +10:00
Stenzek
ee6887b68a
GPU/HW: Add stat for depth buffer clears 2025-03-07 21:33:02 +10:00
Stenzek
2bfbec3e9e
Settings: Use new key name for depth threshold
Because of the changed range.
2025-03-07 21:32:57 +10:00
Stenzek
705fe11e07
Qt: Fix incorrect file filter for memory card import 2025-03-07 21:32:57 +10:00
Stenzek
e73ca9e052
Qt: Fix new range for depth clear threshold 2025-03-07 18:05:20 +10:00
Stenzek
6437a5db60
GPU/TextureCache: Dump textures asynchronously
Saves lagging the emulation.
2025-03-07 16:05:36 +10:00
Stenzek
95797b05f1
GPU: Force early run on GP0 FIFO overflow
And don't crash if a game spams GP0 writes and it overflows.
2025-03-07 15:49:07 +10:00
Stenzek
9939f1cd05
CPU/Interpreter: Use __builtin_{add,sub}_overflow for exceptions
Recompiler already does this.
2025-03-07 15:48:28 +10:00
Stenzek
7eb7ad684c
CPU/Recompiler: Remove load delay flag from lwc2
Shouldn't make any difference since it's not a register write.
2025-03-07 15:08:00 +10:00
Stenzek
826f10239c
CPU/Recompiler: Flush GTE completion cycle on load without fastmem 2025-03-06 22:41:10 +10:00
Stenzek
78bb14bd23
CPU: Fix disassembly of GTE instructions in log 2025-03-06 22:41:10 +10:00
Stenzek
b230c9c639
CPU/PGXP: Use GTE MAX_Z for Z normalization
Fixes clipping with depth buffer in some games, e.g. Final Fantasy VII battles.
2025-03-06 22:41:10 +10:00
Stenzek
8cfd843d8f
System: Fix depth clear threshold not applying immediately 2025-03-06 22:41:09 +10:00
Stenzek
160f1ea8a0
Qt: Fix vibration motor binding not showing some devices 2025-03-06 22:41:09 +10:00
Anderson Cardoso
c3ce0eece2
Atualização Português do Brasil (#3385)
Melhoria de consistência dos textos e adição de novos conforme última atualização mais recente.
2025-03-06 22:40:54 +10:00
140 changed files with 17151 additions and 13657 deletions

View file

@ -2,17 +2,19 @@ name: 🐧 Linux Cross-Compiled AppImage
on:
workflow_call:
inputs:
arch:
required: false
type: string
default: "arm64"
workflow_dispatch:
jobs:
build:
name: "Build"
strategy:
fail-fast: true
matrix:
arch: ["arm64", "armhf"]
runs-on: ubuntu-22.04
container:
image: ghcr.io/duckstation/cross-build-${{ inputs.arch }}:latest
image: ghcr.io/duckstation/cross-build-${{ matrix.arch }}:latest
timeout-minutes: 120
steps:
@ -25,13 +27,13 @@ jobs:
uses: actions/cache@v4
with:
path: ~/deps
key: deps-cross ${{ inputs.arch }} ${{ hashFiles('scripts/deps/build-dependencies-linux.sh', 'scripts/deps/build-dependencies-linux-cross.sh') }}
key: deps-cross ${{ matrix.arch }} ${{ hashFiles('scripts/deps/build-dependencies-linux.sh', 'scripts/deps/build-dependencies-linux-cross.sh') }}
- name: Build Dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: |
scripts/deps/build-dependencies-linux.sh -skip-cleanup "$HOME/deps/host"
scripts/deps/build-dependencies-linux-cross.sh -skip-download "$HOME/deps/host" "${{ inputs.arch }}" "/${{ inputs.arch }}-chroot" "$HOME/deps/cross"
scripts/deps/build-dependencies-linux-cross.sh -skip-download "$HOME/deps/host" "${{ matrix.arch }}" "/${{ matrix.arch }}-chroot" "$HOME/deps/cross"
# Work around container ownership issue
- name: Set Safe Directory
@ -62,7 +64,7 @@ jobs:
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_ASSET "DuckStation-${{ inputs.arch }}.AppImage"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_ASSET "DuckStation-${{ matrix.arch }}.AppImage"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
- name: Tag as Preview Release
@ -91,11 +93,11 @@ jobs:
shell: bash
run: |
cmake --build build --parallel
scripts/packaging/appimage/make-cross-appimage.sh duckstation-qt ${{ inputs.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ inputs.arch }}-chroot"
scripts/packaging/appimage/make-cross-appimage.sh -inject-libc duckstation-mini ${{ inputs.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ inputs.arch }}-chroot"
scripts/packaging/appimage/make-cross-appimage.sh duckstation-qt ${{ matrix.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ matrix.arch }}-chroot"
scripts/packaging/appimage/make-cross-appimage.sh -inject-libc duckstation-mini ${{ matrix.arch }} "$(realpath build)" "$HOME/deps/cross" "/${{ matrix.arch }}-chroot"
- name: Upload AppImages
uses: actions/upload-artifact@v4
with:
name: "linux-${{ inputs.arch }}-appimage"
name: "linux-${{ matrix.arch }}-appimage"
path: "DuckStation-*.AppImage"

View file

@ -68,6 +68,8 @@ jobs:
./artifacts/linux-x64-sse2-appimage/DuckStation-x64-SSE2.AppImage
./artifacts/linux-arm64-appimage/DuckStation-arm64.AppImage
./artifacts/linux-arm64-appimage/DuckStation-Mini-arm64.AppImage
./artifacts/linux-armhf-appimage/DuckStation-armhf.AppImage
./artifacts/linux-armhf-appimage/DuckStation-Mini-armhf.AppImage
./artifacts/linux-flatpak-x86_64/duckstation-x86_64.flatpak
./artifacts/linux-flatpak-aarch64/duckstation-aarch64.flatpak
./artifacts/macos/duckstation-mac-release.zip
@ -91,6 +93,8 @@ jobs:
./artifacts/linux-x64-sse2-appimage/DuckStation-x64-SSE2.AppImage
./artifacts/linux-arm64-appimage/DuckStation-arm64.AppImage
./artifacts/linux-arm64-appimage/DuckStation-Mini-arm64.AppImage
./artifacts/linux-armhf-appimage/DuckStation-armhf.AppImage
./artifacts/linux-armhf-appimage/DuckStation-Mini-armhf.AppImage
./artifacts/linux-flatpak-x86_64/duckstation-x86_64.flatpak
./artifacts/linux-flatpak-aarch64/duckstation-aarch64.flatpak
./artifacts/macos/duckstation-mac-release.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffffff" d="M12 2C17.52 2 22 6.48 22 12C22 17.52 17.52 22 12 22C6.48 22 2 17.52 2 12C2 6.48 6.48 2 12 2ZM12 20C16.42 20 20 16.42 20 12C20 7.58 16.42 4 12 4C7.58 4 4 7.58 4 12C4 16.42 7.58 20 12 20ZM12 11H16V13H12V16L8 12L12 8V11Z"></path></svg>

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffffff" d="M21 3C21.5523 3 22 3.44772 22 4V11H20V5H4V19H10V21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H21ZM21 13C21.5523 13 22 13.4477 22 14V20C22 20.5523 21.5523 21 21 21H13C12.4477 21 12 20.5523 12 20V14C12 13.4477 12.4477 13 13 13H21ZM11.5 7L9.45711 9.04311L11.7071 11.2929L10.2929 12.7071L8.04311 10.4571L6 12.5V7H11.5Z"></path></svg>

After

Width:  |  Height:  |  Size: 424 B

View file

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg1"
sodipodi:docname="exit.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.640625"
inkscape:cx="256"
inkscape:cy="256"
inkscape:window-width="2856"
inkscape:window-height="1651"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
fill="#ffffff"
d="m 363.3838,123.77433 108.17663,108.17663 c 6.3426,6.3426 9.95436,15.06368 9.95436,24.04904 0,8.98535 -3.61176,17.70643 -9.95436,24.04904 L 363.3838,388.22566 c -5.63787,5.63787 -13.21376,8.72108 -21.14202,8.72108 -16.47315,0 -29.86309,-13.38994 -29.86309,-29.86309 V 312.37869 H 199.6213 c -15.59223,0 -28.18935,-12.59711 -28.18935,-28.18935 v -56.37869 c 0,-15.59223 12.59712,-28.18935 28.18935,-28.18935 h 112.75739 v -54.70496 c 0,-16.47315 13.38994,-29.86309 29.86309,-29.86309 7.92826,0 15.50415,3.1713 21.14202,8.72108 z m -191.95185,-8.72108 h -56.3787 c -15.59222,0 -28.189349,12.59712 -28.189349,28.18935 v 225.51479 c 0,15.59223 12.597119,28.18935 28.189349,28.18935 h 56.3787 c 15.59223,0 28.18935,12.59712 28.18935,28.18935 0,15.59223 -12.59712,28.18935 -28.18935,28.18935 h -56.3787 c -46.688603,0 -84.56804,-37.87944 -84.56804,-84.56805 V 143.2426 c 0,-46.688607 37.879437,-84.568041 84.56804,-84.568041 h 56.3787 c 15.59223,0 28.18935,12.597115 28.18935,28.189347 0,15.592224 -12.59712,28.189344 -28.18935,28.189344 z"
id="path1"
style="stroke-width:0.880917" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 640 640"
version="1.1"
id="svg1"
sodipodi:docname="game-list.svg"
width="640"
height="640"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#000001"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.93429246"
inkscape:cx="436.69409"
inkscape:cy="330.73156"
inkscape:window-width="2844"
inkscape:window-height="1651"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
fill="#ffffff"
d="m 264.14016,286.21023 c -46.25894,0 -83.78977,37.53084 -83.78977,83.78977 0,46.25893 37.53083,83.78978 83.78977,83.78978 h 111.71968 c 46.25894,0 83.78977,-37.53085 83.78977,-83.78978 0,-46.25894 -37.53083,-83.78977 -83.78977,-83.78977 z m 132.66713,45.38613 a 17.456203,17.456203 0 1 1 0,34.91239 17.456203,17.456203 0 1 1 0,-34.91239 z m -45.38612,59.35108 a 17.456203,17.456203 0 1 1 34.91239,0 17.456203,17.456203 0 1 1 -34.91239,0 z m -97.75473,-45.38612 c 0,-5.80419 4.66954,-10.47373 10.47372,-10.47373 5.80418,0 10.47371,4.66954 10.47371,10.47373 v 13.96496 h 13.96497 c 5.80417,0 10.47373,4.66953 10.47373,10.47372 0,5.80419 -4.66954,10.47372 -10.47373,10.47372 h -13.96497 v 13.96496 c 0,5.80419 -4.66953,10.47373 -10.47371,10.47373 -5.80419,0 -10.47372,-4.66954 -10.47372,-10.47373 v -13.96496 h -13.96496 c -5.8042,0 -10.47373,-4.66953 -10.47373,-10.47372 0,-5.8042 4.66953,-10.47373 10.47373,-10.47373 h 13.96496 z"
id="path1"
style="stroke-width:0.436405" />
<path
fill="#ffffff"
d="m 89.226718,138.67812 c -9.066093,0 -16.483806,7.41772 -16.483806,16.48381 v 329.67613 c 0,9.06609 7.417713,16.4838 16.483806,16.4838 H 550.77329 c 9.06608,0 16.4838,-7.41771 16.4838,-16.4838 V 155.16193 c 0,-9.06609 -7.41772,-16.48381 -16.4838,-16.48381 z m -65.935226,16.48381 c 0,-36.36739 29.567828,-65.935223 65.935226,-65.935223 H 550.77329 c 36.36739,0 65.93522,29.567833 65.93522,65.935223 v 329.67613 c 0,36.36739 -29.56783,65.93523 -65.93522,65.93523 H 89.226718 c -36.367398,0 -65.935226,-29.56784 -65.935226,-65.93523 z m 98.902838,65.93523 c 0,-18.20753 14.76011,-32.96759 32.96761,-32.96759 18.20751,0 32.96761,14.76006 32.96761,32.96759 0,18.20752 -14.7601,32.96759 -32.96761,32.96759 -18.2075,0 -32.96761,-14.76007 -32.96761,-32.96759 z m 107.14475,0 c 0,-13.70217 11.02352,-24.72572 24.72569,-24.72572 h 230.77329 c 13.70216,0 24.72571,11.02355 24.72571,24.72572 0,13.70217 -11.02355,24.72572 -24.72571,24.72572 H 254.06477 c -13.70217,0 -24.72569,-11.02355 -24.72569,-24.72572 z"
id="path1-5"
style="stroke-width:1.03024"
sodipodi:nodetypes="ssssssssssssssssssssssssssssss" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#ffffff" d="M2.13127 13.6308C1.9492 12.5349 1.95521 11.434 2.13216 10.3695C3.23337 10.3963 4.22374 9.86798 4.60865 8.93871C4.99357 8.00944 4.66685 6.93557 3.86926 6.17581C4.49685 5.29798 5.27105 4.51528 6.17471 3.86911C6.9345 4.66716 8.0087 4.99416 8.93822 4.60914C9.86774 4.22412 10.3961 3.23332 10.369 2.13176C11.4649 1.94969 12.5658 1.9557 13.6303 2.13265C13.6036 3.23385 14.1319 4.22422 15.0612 4.60914C15.9904 4.99406 17.0643 4.66733 17.8241 3.86975C18.7019 4.49734 19.4846 5.27153 20.1308 6.1752C19.3327 6.93499 19.0057 8.00919 19.3907 8.93871C19.7757 9.86823 20.7665 10.3966 21.8681 10.3695C22.0502 11.4654 22.0442 12.5663 21.8672 13.6308C20.766 13.6041 19.7756 14.1324 19.3907 15.0616C19.0058 15.9909 19.3325 17.0648 20.1301 17.8245C19.5025 18.7024 18.7283 19.4851 17.8247 20.1312C17.0649 19.3332 15.9907 19.0062 15.0612 19.3912C14.1316 19.7762 13.6033 20.767 13.6303 21.8686C12.5344 22.0507 11.4335 22.0447 10.3691 21.8677C10.3958 20.7665 9.86749 19.7761 8.93822 19.3912C8.00895 19.0063 6.93508 19.333 6.17532 20.1306C5.29749 19.503 4.51479 18.7288 3.86862 17.8252C4.66667 17.0654 4.99367 15.9912 4.60865 15.0616C4.22363 14.1321 3.23284 13.6038 2.13127 13.6308ZM11.9997 15.0002C13.6565 15.0002 14.9997 13.657 14.9997 12.0002C14.9997 10.3433 13.6565 9.00018 11.9997 9.00018C10.3428 9.00018 8.99969 10.3433 8.99969 12.0002C8.99969 13.657 10.3428 15.0002 11.9997 15.0002Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg1"
sodipodi:docname="start-bios.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.640625"
inkscape:cx="256"
inkscape:cy="256"
inkscape:window-width="2852"
inkscape:window-height="1651"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
fill="#ffffff"
d="m 186.11835,53.343194 c 0,-11.617826 -9.34668,-20.964498 -20.9645,-20.964498 -11.61782,0 -20.9645,9.346672 -20.9645,20.964498 v 34.940832 c -30.83528,0 -55.905324,25.070044 -55.905324,55.905324 H 53.343194 c -11.617826,0 -20.964498,9.34668 -20.964498,20.9645 0,11.61782 9.346672,20.9645 20.964498,20.9645 H 88.284026 V 235.0355 H 53.343194 c -11.617826,0 -20.964498,9.34668 -20.964498,20.96449 0,11.61783 9.346672,20.96451 20.964498,20.96451 h 34.940832 v 48.91716 H 53.343194 c -11.617826,0 -20.964498,9.34666 -20.964498,20.96449 0,11.61783 9.346672,20.9645 20.964498,20.9645 h 34.940832 c 0,30.83528 25.070044,55.90533 55.905324,55.90533 v 34.94082 c 0,11.61783 9.34668,20.9645 20.9645,20.9645 11.61782,0 20.9645,-9.34667 20.9645,-20.9645 v -34.94082 h 48.91715 v 34.94082 c 0,11.61783 9.34668,20.9645 20.96449,20.9645 11.61783,0 20.96451,-9.34667 20.96451,-20.9645 v -34.94082 h 48.91716 v 34.94082 c 0,11.61783 9.34666,20.9645 20.96449,20.9645 11.61783,0 20.9645,-9.34667 20.9645,-20.9645 v -34.94082 c 30.83528,0 55.90533,-25.07005 55.90533,-55.90533 h 34.94082 c 11.61783,0 20.9645,-9.34667 20.9645,-20.9645 0,-11.61783 -9.34667,-20.96449 -20.9645,-20.96449 H 423.71598 V 276.9645 h 34.94082 c 11.61783,0 20.9645,-9.34668 20.9645,-20.96451 0,-11.61781 -9.34667,-20.96449 -20.9645,-20.96449 h -34.94082 v -48.91715 h 34.94082 c 11.61783,0 20.9645,-9.34668 20.9645,-20.9645 0,-11.61782 -9.34667,-20.9645 -20.9645,-20.9645 h -34.94082 c 0,-30.83528 -25.07005,-55.905324 -55.90533,-55.905324 V 53.343194 c 0,-11.617826 -9.34667,-20.964498 -20.9645,-20.964498 -11.61783,0 -20.96449,9.346672 -20.96449,20.964498 V 88.284026 H 276.9645 V 53.343194 c 0,-11.617826 -9.34668,-20.964498 -20.96451,-20.964498 -11.61781,0 -20.96449,9.346672 -20.96449,20.964498 v 34.940832 h -48.91715 z m -13.97634,90.846156 h 167.71598 c 15.46131,0 27.95266,12.49134 27.95266,27.95266 v 167.71598 c 0,15.46131 -12.49135,27.95266 -27.95266,27.95266 H 172.14201 c -15.46132,0 -27.95266,-12.49135 -27.95266,-27.95266 V 172.14201 c 0,-15.46132 12.49134,-27.95266 27.95266,-27.95266 z m 167.71598,27.95266 H 172.14201 v 167.71598 h 167.71598 z"
id="path1"
style="stroke-width:0.87352" />
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 512 512"
version="1.1"
id="svg1"
sodipodi:docname="start-disc.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.8685849"
inkscape:cx="269.72282"
inkscape:cy="294.60797"
inkscape:window-width="2856"
inkscape:window-height="1652"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
fill="#ffffff"
d="m 49.005172,256 a 206.99483,206.99483 0 1 1 413.989658,0 206.99483,206.99483 0 1 1 -413.989658,0 z M 256,281.87436 a 25.874355,25.874355 0 1 1 0,-51.74871 25.874355,25.874355 0 1 1 0,51.74871 z M 178.37694,256 a 77.62306,77.62306 0 1 0 155.24612,0 77.62306,77.62306 0 1 0 -155.24612,0 z m -51.7487,-12.93717 c 0,-28.30008 14.15003,-57.48958 36.54752,-79.88707 22.39749,-22.39749 51.58699,-36.54752 79.88707,-36.54752 7.11544,0 12.93717,-5.82173 12.93717,-12.93718 0,-7.11545 -5.82173,-12.93718 -12.93717,-12.93718 -36.70924,0 -72.12476,18.03119 -98.24169,44.06726 -26.11692,26.03607 -44.06726,61.53245 -44.06726,98.24169 0,7.11544 5.82173,12.93717 12.93718,12.93717 7.11545,0 12.93718,-5.82173 12.93718,-12.93717 z"
id="path1"
style="stroke-width:0.808574" />
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 576 576"
version="1.1"
id="svg1"
sodipodi:docname="start-file.svg"
width="576"
height="576"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.640625"
inkscape:cx="287.81065"
inkscape:cy="225.70414"
inkscape:window-width="2852"
inkscape:window-height="1651"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
fill="#ffffff"
d="m 375.58614,492.32782 h 43.78453 c 10.39883,0 19.97669,-5.47306 25.17611,-14.50363 L 546.71069,302.68606 c 5.29063,-9.03055 5.29063,-20.15913 0.0912,-29.2809 -5.19941,-9.12179 -14.77728,-14.59484 -25.26731,-14.59484 H 156.66347 c -10.39883,0 -19.9767,5.47306 -25.17611,14.50363 L 69.0944,380.22117 V 142.05155 c 0,-8.02715 6.567679,-14.59484 14.594844,-14.59484 H 190.87013 c 3.83116,0 7.57108,1.55071 10.30762,4.28724 l 24.17271,24.17271 c 19.15573,19.15573 45.15279,29.91943 72.24447,29.91943 h 107.1809 c 8.02716,0 14.59484,6.56767 14.59484,14.59484 v 29.1897 h 43.78454 v -29.1897 c 0,-32.19988 -26.17951,-58.37938 -58.37938,-58.37938 h -107.1809 c -15.50702,0 -30.37551,-6.11158 -41.32164,-17.05772 L 232.10058,100.72991 C 221.15444,89.783772 206.28594,83.672181 190.77892,83.672181 H 83.689244 c -32.199876,0 -58.379378,26.179509 -58.379378,58.379369 v 291.89689 c 0,32.19988 26.179502,58.37938 58.379378,58.37938 h 21.618616 z"
id="path1"
style="stroke-width:0.912178" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -3231,7 +3231,7 @@ SCPS-10126:
- AnalogController
- DigitalController
traits:
- ForceAccurateBlending # Requires 16-bit blend precision
- ForceShaderBlending # Requires 16-bit blend precision
- DisableTrueColor # to fix screen flicker.
metadata:
publisher: "Sony"
@ -3257,7 +3257,7 @@ SLES-04108:
- AnalogController
- DigitalController
traits:
- ForceAccurateBlending # Requires 16-bit blend precision
- ForceShaderBlending # Requires 16-bit blend precision
- DisableTrueColor # to fix screen flicker.
metadata:
publisher: "Vivendi Universal Games, Inc"
@ -19400,6 +19400,7 @@ SLES-03552:
traits:
- ForceRecompilerICache
- DisableWidescreen # Breaks sprite sizes.
- DisableFullTrueColor # Breaks menu background colour.
metadata:
publisher: "Capcom"
developer: "Capcom"
@ -19425,6 +19426,7 @@ SLUS-01324:
traits:
- ForceRecompilerICache
- DisableWidescreen # Breaks sprite sizes.
- DisableFullTrueColor # Breaks menu background colour.
metadata:
publisher: "Capcom"
developer: "Capcom"
@ -19447,6 +19449,7 @@ SLPS-02728:
traits:
- ForceRecompilerICache
- DisableWidescreen # Breaks sprite sizes.
- DisableFullTrueColor # Breaks menu background colour.
codes:
- SLPS-02728
- SLPM-87159
@ -20722,6 +20725,8 @@ SLUS-01159:
controllers:
- AnalogController
- DigitalController
traits:
- ForceFullTrueColor # Fixes title/background mismatch in menu.
metadata:
publisher: "Enix"
developer: "Metro 3D"
@ -20809,6 +20814,8 @@ SLPM-86219:
controllers:
- AnalogController
- DigitalController
traits:
- ForceFullTrueColor # Fixes title/background mismatch in menu.
metadata:
publisher: "Enix"
developer: "Metro 3D"
@ -20825,6 +20832,8 @@ SLPM-86219:
linkCable: false
SLPM-80410:
name: "Bust A Move 2 - Dance Tengoku Mix (Japan) (Demo)"
traits:
- ForceFullTrueColor # Fixes title/background mismatch in menu.
metadata:
genre: "** DEMO **"
SCES-01313:
@ -37175,6 +37184,9 @@ SCES-02060:
- AnalogController
- DigitalController
- NeGcon
traits:
- DisableScaledInterlacing # Fixes rendering errors in the main menu.
- ForceDeinterlacing # Fixes rendering errors in the main menu.
metadata:
publisher: "Psygnosis"
developer: "Studio 33 Ltd"
@ -37201,6 +37213,9 @@ SLUS-00912:
- AnalogController
- DigitalController
- NeGcon
traits:
- DisableScaledInterlacing # Fixes rendering errors in the main menu.
- ForceDeinterlacing # Fixes rendering errors in the main menu.
metadata:
publisher: "Psygnosis"
developer: "Studio 33 Ltd"
@ -37217,6 +37232,9 @@ SLUS-00912:
linkCable: false
SLUS-90092:
name: "Destruction Derby Raw (USA) (Demo)"
traits:
- DisableScaledInterlacing # Fixes rendering errors in the main menu.
- ForceDeinterlacing # Fixes rendering errors in the main menu.
metadata:
genre: "** DEMO **"
SLES-04073:
@ -58638,13 +58656,15 @@ SCED-02062:
metadata:
genre: "** DEMO **"
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
SLED-02258:
name: "Final Fantasy VIII (Europe) (Demo 2)"
metadata:
genre: "** DEMO **"
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
SCES-02080:
name: "Final Fantasy VIII (Europe, Australia) (Disc 1)"
discSet:
@ -58661,7 +58681,8 @@ SCES-02080:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
codes:
- SCES-02080
@ -58696,7 +58717,8 @@ SCES-12080:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
codes:
- SCES-12080
@ -58731,7 +58753,8 @@ SCES-22080:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
codes:
- SCES-22080
@ -58766,7 +58789,8 @@ SCES-32080:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
codes:
- SCES-32080
@ -58788,7 +58812,8 @@ SCES-32080:
SLED-02259:
name: "Final Fantasy VIII (France) (Demo)"
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
SLES-02081:
name: "Final Fantasy VIII (France) (Disc 1)"
discSet:
@ -58802,7 +58827,8 @@ SLES-02081:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58831,7 +58857,8 @@ SLES-12081:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58860,7 +58887,8 @@ SLES-22081:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58889,7 +58917,8 @@ SLES-32081:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58918,7 +58947,8 @@ SLES-02082:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58947,7 +58977,8 @@ SLES-12082:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -58976,7 +59007,8 @@ SLES-22082:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59005,7 +59037,8 @@ SLES-32082:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59034,7 +59067,8 @@ SLES-02083:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59063,7 +59097,8 @@ SLES-12083:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59092,7 +59127,8 @@ SLES-22083:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59121,7 +59157,8 @@ SLES-32083:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59152,7 +59189,8 @@ SLPM-87384:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
codes:
- SLPM-87384
- SLPS-01880
@ -59183,7 +59221,8 @@ SLPM-87385:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
codes:
- SLPM-87385
- SLPS-01881
@ -59214,7 +59253,8 @@ SLPM-87386:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
codes:
- SLPM-87386
- SLPS-01882
@ -59245,7 +59285,8 @@ SLPM-87387:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
codes:
- SLPM-87387
- SLPS-01883
@ -59268,13 +59309,15 @@ SLPM-80269:
metadata:
genre: "** DEMO **"
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
SLED-02262:
name: "Final Fantasy VIII (Spain) (Demo)"
metadata:
genre: "** DEMO **"
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
SLES-02084:
name: "Final Fantasy VIII (Spain) (Disc 1)"
discSet:
@ -59291,7 +59334,8 @@ SLES-02084:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59323,7 +59367,8 @@ SLES-12084:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59355,7 +59400,8 @@ SLES-22084:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59387,7 +59433,8 @@ SLES-32084:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
libcrypt: true
metadata:
publisher: "Squaresoft"
@ -59419,7 +59466,8 @@ SLUS-00892:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
metadata:
publisher: "Squaresoft"
developer: "Squaresoft"
@ -59450,7 +59498,8 @@ SLUS-00908:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
metadata:
publisher: "Squaresoft"
developer: "Squaresoft"
@ -59481,7 +59530,8 @@ SLUS-00909:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
metadata:
publisher: "Squaresoft"
developer: "Squaresoft"
@ -59512,7 +59562,8 @@ SLUS-00910:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
metadata:
publisher: "Squaresoft"
developer: "Squaresoft"
@ -59533,7 +59584,8 @@ SCPS-45375:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
codes:
- SCPS-45375
- SCPS-45376
@ -72902,7 +72954,7 @@ SLUS-01244:
- AnalogController
- DigitalController
traits:
- ForceAccurateBlending # Requires 16-bit blend precision
- ForceShaderBlending # Requires 16-bit blend precision
- DisableTrueColor # to fix transparency in menu backgrounds.
metadata:
publisher: "The 3DO Company"
@ -76080,7 +76132,6 @@ SLPS-01029:
SCPS-10029:
name: "I.Q - Intelligent Qube (Japan)"
controllers:
- AnalogController
- DigitalController
codes:
- SCPS-10029
@ -76101,6 +76152,8 @@ SCPS-10029:
linkCable: false
PCPX-96058:
name: "I.Q - Intelligent Qube (Japan) (Demo)"
controllers:
- DigitalController
metadata:
genre: "** DEMO **"
SCUS-94181:
@ -76109,7 +76162,6 @@ SCUS-94181:
rating: NoIssues
comments: "Play field occasionally jerks with PGXP switched on"
controllers:
- AnalogController
- DigitalController
metadata:
publisher: "Sony"
@ -76127,12 +76179,13 @@ SCUS-94181:
linkCable: false
SCUS-94196:
name: "I.Q - Intelligent Qube (USA) (Demo)"
controllers:
- DigitalController
metadata:
genre: "** DEMO **"
SCPS-45036:
name: "I.Q - Intelligent Qube (aka I.Q. - Intelligent Qube)"
controllers:
- AnalogController
- DigitalController
metadata:
publisher: "Sony"
@ -81439,6 +81492,8 @@ SLUS-01383:
controllers:
- AnalogController
- DigitalController
traits:
- DisableFullTrueColor # Breaks loading screen background colour.
metadata:
publisher: "Knowledge Adventure"
developer: "Digital Illusions CE, Canada"
@ -100991,6 +101046,9 @@ SCPS-45320:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
codes:
- SCPS-45320
- SCPS-45321
@ -101018,6 +101076,9 @@ PAPX-90044:
versionTested: "0.1-905-g237f469"
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
SCPS-45317:
name: "Metal Gear Solid (Japan, Asia) (Disc 1) (Ichi) (Premium Package)"
discSet:
@ -101030,6 +101091,9 @@ SCPS-45317:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
codes:
- SCPS-45317
- SLPM-86111
@ -101059,6 +101123,9 @@ SCPS-45318:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
codes:
- SCPS-45318
- SLPM-86112
@ -101082,12 +101149,18 @@ SLED-01400:
genre: "** DEMO **"
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
SLED-01775:
name: "Metal Gear Solid (Europe) (Demo 2)"
metadata:
genre: "** DEMO **"
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
SLES-01370:
name: "Metal Gear Solid (Europe) (Disc 1)"
discSet:
@ -101103,6 +101176,9 @@ SLES-01370:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101132,6 +101208,9 @@ SLES-11370:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101161,6 +101240,9 @@ SLES-01506:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101190,6 +101272,9 @@ SLES-11506:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101219,6 +101304,9 @@ SLES-01507:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101248,6 +101336,9 @@ SLES-11507:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101277,6 +101368,9 @@ SLES-01508:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101306,6 +101400,9 @@ SLES-11508:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101327,6 +101424,9 @@ SLPM-86098:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "Konami / Diamond Head"
@ -101353,6 +101453,9 @@ SLPM-86114:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
codes:
- SLPM-86114
- SLPM-87411
@ -101382,6 +101485,9 @@ SLPM-86115:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
codes:
- SLPM-86115
- SLPM-87412
@ -101414,6 +101520,9 @@ SLES-01734:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101443,6 +101552,9 @@ SLES-11734:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101463,6 +101575,9 @@ SLUS-90035:
genre: "** DEMO **"
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
SLUS-00594:
name: "Metal Gear Solid (USA) (Disc 1)"
discSet:
@ -101478,6 +101593,9 @@ SLUS-00594:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -101507,6 +101625,9 @@ SLUS-00776:
- DigitalController
traits:
- ForcePGXPCPUMode
settings:
cdromMaxSeekSpeedupCycles: 30000
cdromMaxReadSpeedupCycles: 80000
metadata:
publisher: "Konami"
developer: "KCE Japan"
@ -155761,7 +155882,8 @@ SLUS-90029:
- AnalogController
- DigitalController
traits:
- DisableWidescreen
- DisableWidescreen # Breaks background rendering.
- DisableScaledInterlacing # Breaks intro rendering.
metadata:
publisher: "Squaresoft"
developer: "Squaresoft"

View file

@ -6,3 +6,9 @@ IndentPPDirectives: AfterHash
IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false
AllowShortCaseLabelsOnASingleLine: true
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: true
AcrossComments: true
AlignCaseColons: false

View file

@ -27,7 +27,13 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
if(CMAKE_GENERATOR STREQUAL "Ninja")
# Ninja dyndep expects the .ifc output to be located in a specific relative path
file(RELATIVE_PATH BMI_DIR "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir")
else()
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
@ -69,8 +75,6 @@ function(add_module_library name)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
target_compile_definitions(${name} PRIVATE FMT_MODULE)
if (FMT_USE_CMAKE_MODULES)
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
FILES ${sources})
@ -240,7 +244,13 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
-Wnull-dereference -Wduplicated-cond)
-Wduplicated-cond)
# Workaround for GCC regression
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
endif ()
endif ()
set(WERROR_FLAG -Werror)
endif ()
@ -289,6 +299,7 @@ function(add_headers VAR)
endfunction()
# Define the fmt library, its includes and the needed defines.
set(FMT_HEADERS)
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h)
@ -319,7 +330,7 @@ else ()
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
endif ()
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -359,8 +370,8 @@ if (NOT MSVC)
# Unicode is always supported on compilers other than MSVC.
elseif (FMT_UNICODE)
# Unicode support requires compiling with /utf-8.
target_compile_options(fmt PUBLIC $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<COMPILE_LANGUAGE:CXX>:/utf-8>)
target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
else ()
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
endif ()
@ -369,7 +380,7 @@ target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
target_include_directories(fmt-header-only
${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE
${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
@ -420,7 +431,9 @@ if (FMT_INSTALL)
endif()
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt-core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
@ -433,13 +446,15 @@ if (FMT_INSTALL)
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files.
install(
FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR})
install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR}
COMPONENT fmt-core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::)
NAMESPACE fmt::
COMPONENT fmt-core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT fmt-core)
endif ()
function(add_doc_target)
@ -475,7 +490,8 @@ function(add_doc_target)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL)
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt-doc OPTIONAL)
endfunction()
if (FMT_DOC)

View file

@ -1,3 +1,322 @@
# 11.1.4 - 2025-02-26
- Fixed ABI compatibility with earlier 11.x versions on Windows
(https://github.com/fmtlib/fmt/issues/4359).
- Improved the logic of switching between fixed and exponential format for
`float` (https://github.com/fmtlib/fmt/issues/3649).
- Moved `is_compiled_string` to the public API
(https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
- Simplified implementation of `operator""_cf`
(https://github.com/fmtlib/fmt/pull/4349). Thanks @LocalSpook.
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
Thanks @LocalSpook.
- Fixed handling of BMI paths with the Ninja generator
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.
- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331,
https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356).
Thanks @dinomight.
# 11.1.3 - 2025-01-25
- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313).
- Worked around an internal compiler error when using C++20 modules with GCC
14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295).
- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318).
- Fixed an issue caused by instantiating `formatter<const T>`
(https://github.com/fmtlib/fmt/issues/4303,
https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp.
- Fixed formatting into `std::ostreambuf_iterator` when using format string
compilation (https://github.com/fmtlib/fmt/issues/4309,
https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus.
- Restored a constraint on the map formatter so that it correctly reports as
unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326).
Thanks @timsong-cpp.
- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298).
- Readded `args()` to `fmt::format_context`
(https://github.com/fmtlib/fmt/issues/4307,
https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314,
https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt.
- Fixed a pedantic mode error in the CMake config
(https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik.
# 11.1.2 - 2025-01-12
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4292).
- Added `wchar_t` support to the `std::bitset` formatter
(https://github.com/fmtlib/fmt/issues/4285,
https://github.com/fmtlib/fmt/pull/4286,
https://github.com/fmtlib/fmt/issues/4289,
https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus.
- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
`add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283).
- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291).
Thanks @trim21.
- Fixed a compilation error in chrono on nvcc
(https://github.com/fmtlib/fmt/issues/4297,
https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml.
- Fixed various warnings
(https://github.com/fmtlib/fmt/pull/4288,
https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300.
# 11.1.1 - 2024-12-27
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4278).
- Defined CMake components (`core` and `doc`) to allow docs to be installed
separately (https://github.com/fmtlib/fmt/pull/4276).
Thanks @carlsmedstad.
# 11.1.0 - 2024-12-25
- Improved C++20 module support
(https://github.com/fmtlib/fmt/issues/4081,
https://github.com/fmtlib/fmt/pull/4083,
https://github.com/fmtlib/fmt/pull/4084,
https://github.com/fmtlib/fmt/pull/4152,
https://github.com/fmtlib/fmt/issues/4153,
https://github.com/fmtlib/fmt/pull/4169,
https://github.com/fmtlib/fmt/issues/4190,
https://github.com/fmtlib/fmt/issues/4234,
https://github.com/fmtlib/fmt/pull/4239).
Thanks @kamrann and @Arghnews.
- Reduced debug (unoptimized) binary code size and the number of template
instantiations when passing formatting arguments. For example, unoptimized
binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
~60% on clang (x86-64).
GCC:
- Before: 161 instructions of which 105 are in reusable functions
([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
- After: 116 instructions of which 60 are in reusable functions
([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
Clang:
- Before: 310 instructions of which 251 are in reusable functions
([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
- After: 194 instructions of which 135 are in reusable functions
([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
- Added an experimental `fmt::writer` API that can be used for writing to
different destinations such as files or strings
(https://github.com/fmtlib/fmt/issues/2354).
For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
```c++
#include <fmt/os.h>
void write_text(fmt::writer w) {
w.print("The answer is {}.", 42);
}
int main() {
// Write to FILE.
write_text(stdout);
// Write to fmt::ostream.
auto f = fmt::output_file("myfile");
write_text(f);
// Write to std::string.
auto sb = fmt::string_buffer();
write_text(sb);
std::string s = sb.str();
}
```
- Added width and alignment support to the formatter of `std::error_code`.
- Made `std::expected<void, E>` formattable
(https://github.com/fmtlib/fmt/issues/4145,
https://github.com/fmtlib/fmt/pull/4148).
For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
```c++
fmt::print("{}", std::expected<void, int>());
```
prints
```
expected()
```
Thanks @phprus.
- Made `fmt::is_formattable<void>` SFINAE-friendly
(https://github.com/fmtlib/fmt/issues/4147).
- Added support for `_BitInt` formatting when using clang
(https://github.com/fmtlib/fmt/issues/4007,
https://github.com/fmtlib/fmt/pull/4072,
https://github.com/fmtlib/fmt/issues/4140,
https://github.com/fmtlib/fmt/issues/4173,
https://github.com/fmtlib/fmt/pull/4176).
For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
```c++
using int42 = _BitInt(42);
fmt::print("{}", int42(100));
```
Thanks @Arghnews.
- Added the `n` specifier for tuples and pairs
(https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc.
- Added support for tuple-like types to `fmt::join`
(https://github.com/fmtlib/fmt/issues/4226,
https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus.
- Made more types formattable at compile time
(https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH.
- Implemented a more efficient compile-time `fmt::formatted_size`
(https://github.com/fmtlib/fmt/issues/4102,
https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus.
- Fixed compile-time formatting of some string types
(https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd.
- Made compiled version of `fmt::format_to` work with
`std::back_insert_iterator<std::vector<char>>`
(https://github.com/fmtlib/fmt/issues/4206,
https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus.
- Added a formatter for `std::reference_wrapper`
(https://github.com/fmtlib/fmt/pull/4163,
https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus.
- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH.
- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
disabled (https://github.com/fmtlib/fmt/issues/4088).
- Fixed an unreleased regression in transcoding of surrogate pairs
(https://github.com/fmtlib/fmt/issues/4094,
https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus.
- Made `fmt::appender` satisfy `std::output_iterator` concept
(https://github.com/fmtlib/fmt/issues/4092,
https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus.
- Made `std::iterator_traits<fmt::appender>` standard-conforming
(https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter.
- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
an implicit conversion to `std::string_view`
(https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
(https://github.com/fmtlib/fmt/issues/4257,
https://github.com/fmtlib/fmt/pull/4258,
https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus.
- Fixed compatibility with platforms that use 80-bit `long double`
(https://github.com/fmtlib/fmt/issues/4245,
https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma.
- Added support for UTF-32 code units greater than `0xFFFF` in fill
(https://github.com/fmtlib/fmt/issues/4201).
- Fixed handling of legacy encodings on Windows with GCC
(https://github.com/fmtlib/fmt/issues/4162).
- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
(https://github.com/fmtlib/fmt/issues/4261,
https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel.
- Added `fmt::dynamic_format_arg_store::size`
(https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch.
- Removed the ability to control locale usage via an undocumented
`FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
similar macros.
- Improved include directory ordering to reduce the chance of including
incorrect headers when using multiple versions of {fmt}
(https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan.
- Made it possible to compile a subset of {fmt} without the C++ runtime.
- Improved documentation and README
(https://github.com/fmtlib/fmt/pull/4066,
https://github.com/fmtlib/fmt/issues/4117,
https://github.com/fmtlib/fmt/issues/4203,
https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh.
- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110,
https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado.
- Improved CI (https://github.com/fmtlib/fmt/pull/4155,
https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/issues/2708,
https://github.com/fmtlib/fmt/issues/4091,
https://github.com/fmtlib/fmt/issues/4109,
https://github.com/fmtlib/fmt/issues/4113,
https://github.com/fmtlib/fmt/issues/4125,
https://github.com/fmtlib/fmt/issues/4129,
https://github.com/fmtlib/fmt/pull/4130,
https://github.com/fmtlib/fmt/pull/4131,
https://github.com/fmtlib/fmt/pull/4132,
https://github.com/fmtlib/fmt/issues/4133,
https://github.com/fmtlib/fmt/issues/4144,
https://github.com/fmtlib/fmt/issues/4150,
https://github.com/fmtlib/fmt/issues/4158,
https://github.com/fmtlib/fmt/pull/4159,
https://github.com/fmtlib/fmt/issues/4160,
https://github.com/fmtlib/fmt/pull/4170,
https://github.com/fmtlib/fmt/issues/4177,
https://github.com/fmtlib/fmt/pull/4187,
https://github.com/fmtlib/fmt/pull/4188,
https://github.com/fmtlib/fmt/pull/4194,
https://github.com/fmtlib/fmt/pull/4200,
https://github.com/fmtlib/fmt/issues/4205,
https://github.com/fmtlib/fmt/issues/4207,
https://github.com/fmtlib/fmt/pull/4208,
https://github.com/fmtlib/fmt/pull/4210,
https://github.com/fmtlib/fmt/issues/4220,
https://github.com/fmtlib/fmt/issues/4231,
https://github.com/fmtlib/fmt/issues/4232,
https://github.com/fmtlib/fmt/pull/4233,
https://github.com/fmtlib/fmt/pull/4236,
https://github.com/fmtlib/fmt/pull/4267,
https://github.com/fmtlib/fmt/pull/4271).
Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
@c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.
# 11.0.2 - 2024-07-20
- Fixed compatibility with non-POSIX systems
@ -268,6 +587,9 @@
- Fixed handling of negative ids in `fmt::basic_format_args::get`
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
- Fixed handling of a buffer boundary on flush
(https://github.com/fmtlib/fmt/issues/4229).
- Improved named argument validation
(https://github.com/fmtlib/fmt/issues/3817).

View file

@ -291,6 +291,7 @@ converts to `std::print`.)
- [ccache](https://ccache.dev/): a compiler cache
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
analytical database management system
- [ContextVision](https://www.contextvision.com/): medical imaging software
- [Contour](https://github.com/contour-terminal/contour/): a modern
terminal emulator
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous

View file

@ -17,7 +17,6 @@
#include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
@ -72,19 +71,13 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`.
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
template <typename Context> class dynamic_format_arg_store {
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
detail::mapped_type_constant<T, char_type>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
@ -97,7 +90,7 @@ class dynamic_format_arg_store
};
template <typename T>
using stored_type = conditional_t<
using stored_t = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
@ -112,41 +105,37 @@ class dynamic_format_arg_store
friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
data_.emplace_back(arg);
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
if (named_info_.empty())
data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.emplace_back(detail::unwrap(arg.value));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
data_[0] = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/**
* Adds an argument into the dynamic store for later passing to a formatting
* function.
@ -164,7 +153,7 @@ class dynamic_format_arg_store
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
@ -200,7 +189,7 @@ class dynamic_format_arg_store
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
@ -210,17 +199,20 @@ class dynamic_format_arg_store
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
dynamic_args_ = {};
}
/// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
"set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
/// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); }
};
FMT_END_NAMESPACE

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -330,7 +330,7 @@ FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
FMT_CONSTEXPR ansi_color_escape(color_type text_color,
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
@ -412,13 +412,13 @@ template <typename Char> struct ansi_color_escape {
};
template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept
FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
}
template <typename Char>
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept
FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
-> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;");
}
@ -434,36 +434,35 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
buffer.append(reset_color.begin(), reset_color.end());
}
template <typename T> struct styled_arg : detail::view {
template <typename T> struct styled_arg : view {
const T& value;
text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {}
};
template <typename Char>
void vformat_to(
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffered_context<type_identity_t<Char>>> args) {
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
auto foreground = make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
auto background = make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args, {});
if (has_style) detail::reset_color<Char>(buf);
vformat_to(buf, fmt, args);
if (has_style) reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
@ -485,7 +484,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
template <typename... T>
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
T&&... args) {
vprint(f, ts, fmt, fmt::make_format_args(args...));
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
}
/**
@ -524,7 +523,7 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
template <typename... T>
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
-> std::string {
return fmt::vformat(ts, fmt, fmt::make_format_args(args...));
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
}
/// Formats a string with the given text_style and writes the output to `out`.
@ -551,7 +550,7 @@ template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, const text_style& ts,
format_string<T...> fmt, T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt, fmt::make_format_args(args...));
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
}
template <typename T, typename Char>
@ -560,31 +559,30 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) {
const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out();
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out);
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
}
if (ts.has_foreground()) {
has_style = true;
auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out);
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
}
if (ts.has_background()) {
has_style = true;
auto background =
detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out);
out = detail::copy<Char>(background.begin(), background.end(), out);
}
out = formatter<T, Char>::format(value, ctx);
out = formatter<T, Char>::format(arg.value, ctx);
if (has_style) {
auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out);
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
}
return out;
}

View file

@ -19,17 +19,11 @@ FMT_BEGIN_NAMESPACE
// A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {};
namespace detail {
template <typename T, typename InputIt>
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
-> counting_iterator {
return it + (end - begin);
}
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
namespace detail {
/**
* Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17
@ -42,22 +36,11 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& {
return value;
@ -77,6 +60,29 @@ constexpr const auto& get([[maybe_unused]] const T& first,
return detail::get<N - 1>(rest...);
}
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
@ -149,8 +155,9 @@ template <typename Char, typename T, int N> struct field {
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else {
return write<Char>(out, arg);
}
return write<Char>(out, arg);
}
};
@ -275,6 +282,7 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
}
template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id;
constexpr int on_auto() {
@ -282,25 +290,28 @@ template <typename Char> struct arg_id_handler {
return 0;
}
constexpr int on_index(int id) {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
@ -363,18 +374,18 @@ constexpr auto compile_format_string(S fmt) {
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
if constexpr (arg_id_result.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
constexpr auto arg_index = arg_id_result.arg_id.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
fmt);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
if constexpr (arg_index >= 0) {
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
@ -383,8 +394,7 @@ constexpr auto compile_format_string(S fmt) {
arg_index, next_id>(fmt);
} else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
fmt);
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
@ -405,7 +415,7 @@ constexpr auto compile_format_string(S fmt) {
}
template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) {
@ -441,7 +451,7 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) {
@ -468,7 +478,7 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
@ -483,7 +493,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
@ -493,32 +503,32 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
-> size_t {
return fmt::format_to(detail::counting_iterator(), fmt, args...).count();
auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, args...);
return buf.count();
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) {
memory_buffer buffer;
fmt::format_to(std::back_inserter(buffer), fmt, args...);
detail::print(f, {buffer.data(), buffer.size()});
auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, args...);
detail::print(f, {buf.data(), buf.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) {
print(stdout, fmt, args...);
}
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
} // namespace literals
#endif

View file

@ -14,10 +14,6 @@
# include <climits>
# include <cmath>
# include <exception>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# include <locale>
# endif
#endif
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@ -26,16 +22,22 @@
#include "format.h"
#if FMT_USE_LOCALE
# include <locale>
#endif
#ifndef FMT_FUNC
# define FMT_FUNC
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
// Chosen instead of std::abort to satisfy Clang in CUDA mode during device
// code pass.
std::terminate();
// writing to stderr fails.
fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort();
}
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
@ -61,86 +63,95 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
FMT_ASSERT(out.size() <= inline_buffer_size, "");
}
FMT_FUNC void report_error(format_func func, int error_code,
const char* message) noexcept {
FMT_FUNC void do_report_error(format_func func, int error_code,
const char* message) noexcept {
memory_buffer full_message;
func(full_message, error_code, message);
// Don't use fwrite_fully because the latter may throw.
// Don't use fwrite_all because the latter may throw.
if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
std::fputc('\n', stderr);
}
// A wrapper around fwrite that throws on error.
inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) {
inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
size_t written = std::fwrite(ptr, 1, count, stream);
if (written < count)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
#if FMT_USE_LOCALE
using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
static_assert(std::is_same<Locale, locale>::value, "");
}
#else
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, std::locale>::value, "");
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto& facet = std::use_facet<std::numpunct<Char>>(loc.get<std::locale>());
auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
auto grouping = facet.grouping();
auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
return {std::move(grouping), thousands_sep};
}
template <typename Char>
FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
.decimal_point();
return use_facet<numpunct<Char>>(loc.get<locale>()).decimal_point();
}
#else
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result<Char> {
return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR};
}
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
#if FMT_USE_LOCALE
FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#ifdef FMT_STATIC_THOUSANDS_SEPARATOR
value.visit(loc_writer<>{
out, specs, std::string(1, FMT_STATIC_THOUSANDS_SEPARATOR), "\3", "."});
return true;
#else
auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding.
using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale))
return std::use_facet<facet>(locale).put(out, value, specs);
return use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs);
#endif
}
#endif
} // namespace detail
FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS
// Use FMT_THROW instead of throw to avoid bogus unreachable code warnings
// from MSVC.
FMT_THROW(format_error(message));
#else
fputs(message, stderr);
abort();
#endif
}
template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc);
grouping_ = numpunct.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep());
auto& np = detail::use_facet<detail::numpunct<char>>(loc);
grouping_ = np.grouping();
if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep());
}
#if FMT_USE_LOCALE
template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs& specs) const -> bool {
@ -1425,7 +1436,7 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
FMT_FUNC void report_system_error(int error_code,
const char* message) noexcept {
report_error(format_system_error, error_code, message);
do_report_error(format_system_error, error_code, message);
}
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
@ -1438,6 +1449,15 @@ FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
namespace detail {
FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
locale_ref loc) {
auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string(
fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}});
}
template <typename T> struct span {
T* data;
size_t size;
@ -1508,6 +1528,7 @@ template <typename F> class glibc_file : public file_base<F> {
void init_buffer() {
if (this->file_->_IO_write_ptr) return;
// Force buffer initialization by placing and removing a char in a buffer.
assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);
putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr;
}
@ -1615,7 +1636,7 @@ template <typename F> class fallback_file : public file_base<F> {
};
#ifndef FMT_USE_FALLBACK_FILE
# define FMT_USE_FALLBACK_FILE 1
# define FMT_USE_FALLBACK_FILE 0
#endif
template <typename F,
@ -1692,7 +1713,7 @@ FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args,
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args);
if (newline) buffer.push_back('\n');
fwrite_fully(buffer.data(), buffer.size(), f);
fwrite_all(buffer.data(), buffer.size(), f);
}
#endif
@ -1704,7 +1725,7 @@ FMT_FUNC void print(std::FILE* f, string_view text) {
if (write_console(fd, text)) return;
}
#endif
fwrite_fully(text.data(), text.size(), f);
fwrite_all(text.data(), text.size(), f);
}
} // namespace detail

File diff suppressed because it is too large Load diff

View file

@ -118,7 +118,7 @@ FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept;
}
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
format_args args);
/**
@ -146,10 +146,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
* "cannot open file '{}'", filename);
* }
*/
template <typename... Args>
std::system_error windows_error(int error_code, string_view message,
const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
template <typename... T>
auto windows_error(int error_code, string_view message, const T&... args)
-> std::system_error {
return vwindows_error(error_code, message, vargs<T...>{{args...}});
}
// Reports a Windows error without throwing an exception.
@ -164,8 +164,8 @@ inline auto system_category() noexcept -> const std::error_category& {
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
}
#endif
@ -176,24 +176,24 @@ class buffered_file {
friend class file;
explicit buffered_file(FILE* f) : file_(f) {}
inline explicit buffered_file(FILE* f) : file_(f) {}
public:
buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {}
inline buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept;
public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr;
}
auto operator=(buffered_file&& other) -> buffered_file& {
inline auto operator=(buffered_file&& other) -> buffered_file& {
close();
file_ = other.file_;
other.file_ = nullptr;
@ -207,13 +207,13 @@ class buffered_file {
FMT_API void close();
// Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; }
inline auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int;
template <typename... T>
inline void print(string_view fmt, const T&... args) {
const auto& vargs = fmt::make_format_args(args...);
fmt::vargs<T...> vargs = {{args...}};
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
: fmt::vprint(file_, fmt, vargs);
}
@ -248,7 +248,7 @@ class FMT_API file {
};
// Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {}
inline file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag);
@ -257,10 +257,10 @@ class FMT_API file {
file(const file&) = delete;
void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& {
inline auto operator=(file&& other) -> file& {
close();
fd_ = other.fd_;
other.fd_ = -1;
@ -271,7 +271,7 @@ class FMT_API file {
~file() noexcept;
// Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; }
inline auto descriptor() const noexcept -> int { return fd_; }
// Closes the file.
void close();
@ -324,9 +324,9 @@ auto getpagesize() -> long;
namespace detail {
struct buffer_size {
buffer_size() = default;
constexpr buffer_size() = default;
size_t value = 0;
auto operator=(size_t val) const -> buffer_size {
FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size();
bs.value = val;
return bs;
@ -337,7 +337,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
constexpr ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@ -358,59 +358,47 @@ struct ostream_params {
# endif
};
class file_buffer final : public buffer<char> {
} // namespace detail
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream : private detail::buffer<char> {
private:
file file_;
FMT_API static void grow(buffer<char>& buf, size_t);
ostream(cstring_view path, const detail::ostream_params& params);
static void grow(buffer<char>& buf, size_t);
public:
FMT_API file_buffer(cstring_view path, const ostream_params& params);
FMT_API file_buffer(file_buffer&& other) noexcept;
FMT_API ~file_buffer();
ostream(ostream&& other) noexcept;
~ostream();
void flush() {
operator writer() {
detail::buffer<char>& buf = *this;
return buf;
}
inline void flush() {
if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0]));
clear();
}
void close() {
flush();
file_.close();
}
};
} // namespace detail
constexpr auto buffer_size = detail::buffer_size();
/// A fast output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); }
inline void close() {
flush();
file_.close();
}
/// Formats `args` according to specifications in `fmt` and writes the
/// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...));
vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
}
};

View file

@ -22,6 +22,14 @@
#include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
@ -35,53 +43,18 @@ class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
};
#if FMT_MSC_VERSION
#if FMT_MSVC_STL_UPDATE
template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*;
#endif
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
using unsigned_streamsize = make_unsigned_t<std::streamsize>;
unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do {
@ -92,21 +65,9 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
} while (size != 0);
}
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view {
const T& value;
};
} // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<.
@ -117,7 +78,11 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value);
auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
auto&& output = std::basic_ostream<Char>(&formatbuf);
output.imbue(std::locale::classic()); // The default is always unlocalized.
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx);
}
@ -148,24 +113,30 @@ constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value};
}
namespace detail {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
inline void vprint(std::ostream& os, string_view fmt, format_args args) {
auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
} // namespace detail
FMT_EXPORT template <typename Char>
void vprint(std::basic_ostream<Char>& os,
basic_string_view<type_identity_t<Char>> format_str,
typename detail::vformat_args<Char>::type args) {
auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return;
detail::vformat_to(buffer, fmt, args);
FILE* f = nullptr;
#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
}
}
#endif
detail::ignore_unused(f);
detail::write_buffer(os, buffer);
}
@ -178,19 +149,11 @@ void vprint(std::basic_ostream<Char>& os,
*/
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...);
if (detail::use_utf8())
vprint(os, fmt, vargs);
else
detail::vprint_directly(os, fmt, vargs);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
fmt::vargs<T...> vargs = {{args...}};
if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt.str, vargs);
detail::write_buffer(os, buffer);
}
FMT_EXPORT template <typename... T>
@ -198,14 +161,6 @@ void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
FMT_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
}
FMT_END_NAMESPACE
#endif // FMT_OSTREAM_H_

View file

@ -33,8 +33,9 @@ template <typename Char> class basic_printf_context {
public:
using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>;
using parse_context_type = parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes.
@ -54,6 +55,23 @@ template <typename Char> class basic_printf_context {
namespace detail {
// Return the result via the out param to workaround gcc bug 77539.
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
for (out = first; out != last; ++out) {
if (*out == value) return true;
}
return false;
}
template <>
inline auto find<false, char>(const char* first, const char* last, char value,
const char*& out) -> bool {
out =
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
return out != nullptr;
}
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
@ -61,7 +79,7 @@ template <bool IsSigned> struct int_checker {
unsigned max = to_unsigned(max_value<int>());
return value <= max;
}
static auto fits_in_int(bool) -> bool { return true; }
inline static auto fits_in_int(bool) -> bool { return true; }
};
template <> struct int_checker<true> {
@ -69,7 +87,7 @@ template <> struct int_checker<true> {
return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>();
}
static auto fits_in_int(int) -> bool { return true; }
inline static auto fits_in_int(int) -> bool { return true; }
};
struct printf_precision_handler {
@ -127,25 +145,19 @@ template <typename T, typename Context> class arg_converter {
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings.
if (is_signed) {
auto n = static_cast<int>(static_cast<target_type>(value));
arg_ = detail::make_arg<Context>(n);
} else {
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
if (is_signed)
arg_ = static_cast<int>(static_cast<target_type>(value));
else
arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
} else {
if (is_signed) {
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
auto n = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n);
} else {
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
// glibc's printf doesn't sign extend arguments of smaller types:
// std::printf("%lld", -42); // prints "4294967254"
// but we don't have to do the same because it's a UB.
if (is_signed)
arg_ = static_cast<long long>(value);
else
arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
}
}
@ -172,8 +184,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
arg_ = static_cast<typename Context::char_type>(value);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -194,13 +205,13 @@ class printf_width_handler {
format_specs& specs_;
public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) {
specs_.align = align::left;
specs_.set_align(align::left);
width = 0 - width;
}
unsigned int_max = to_unsigned(max_value<int>());
@ -234,69 +245,74 @@ class printf_arg_formatter : public arg_formatter<Char> {
void write_null_pointer(bool is_string = false) {
auto s = this->specs;
s.type = presentation_type::none;
s.set_type(presentation_type::none);
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
}
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); }
void operator()(monostate value) { write(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead.
if (!std::is_same<T, Char>::value) {
base::operator()(value);
write(value);
return;
}
format_specs s = this->specs;
if (s.type != presentation_type::none && s.type != presentation_type::chr) {
if (s.type() != presentation_type::none &&
s.type() != presentation_type::chr) {
return (*this)(static_cast<int>(value));
}
s.sign = sign::none;
s.alt = false;
s.fill = ' '; // Ignore '0' flag for char types.
s.set_sign(sign::none);
s.clear_alt();
s.set_fill(' '); // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types
if (s.align == align::none || s.align == align::numeric)
s.align = align::right;
write<Char>(this->out, static_cast<Char>(value), s);
if (s.align() == align::none || s.align() == align::numeric)
s.set_align(align::right);
detail::write<Char>(this->out, static_cast<Char>(value), s);
}
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) {
base::operator()(value);
write(value);
}
void operator()(const char* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
write_null_pointer(this->specs.type() != presentation_type::pointer);
}
void operator()(const wchar_t* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer(this->specs.type != presentation_type::pointer);
write_null_pointer(this->specs.type() != presentation_type::pointer);
}
void operator()(basic_string_view<Char> value) { base::operator()(value); }
void operator()(basic_string_view<Char> value) { write(value); }
void operator()(const void* value) {
if (value)
base::operator()(value);
write(value);
else
write_null_pointer();
}
void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({});
auto parse_ctx = parse_context<Char>({});
handle.format(parse_ctx, context_);
}
};
@ -305,23 +321,14 @@ template <typename Char>
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) {
switch (*it) {
case '-':
specs.align = align::left;
break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill = '0';
break;
case '-': specs.set_align(align::left); break;
case '+': specs.set_sign(sign::plus); break;
case '0': specs.set_fill('0'); break;
case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space;
if (specs.sign() != sign::plus) specs.set_sign(sign::space);
break;
case '#':
specs.alt = true;
break;
default:
return;
case '#': specs.set_alt(); break;
default: return;
}
}
}
@ -339,7 +346,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs,
++it;
arg_index = value != -1 ? value : max_value<int>();
} else {
if (c == '0') specs.fill = '0';
if (c == '0') specs.set_fill('0');
if (value != 0) {
// Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now.
@ -369,43 +376,22 @@ inline auto parse_printf_presentation_type(char c, type t, bool& upper)
using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) {
case 'd':
return in(t, integral_set) ? pt::dec : pt::none;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'X':
upper = true;
FMT_FALLTHROUGH;
case 'x':
return in(t, integral_set) ? pt::hex : pt::none;
case 'E':
upper = true;
FMT_FALLTHROUGH;
case 'e':
return in(t, float_set) ? pt::exp : pt::none;
case 'F':
upper = true;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G':
upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
case 'd': return in(t, integral_set) ? pt::dec : pt::none;
case 'o': return in(t, integral_set) ? pt::oct : pt::none;
case 'X': upper = true; FMT_FALLTHROUGH;
case 'x': return in(t, integral_set) ? pt::hex : pt::none;
case 'E': upper = true; FMT_FALLTHROUGH;
case 'e': return in(t, float_set) ? pt::exp : pt::none;
case 'F': upper = true; FMT_FALLTHROUGH;
case 'f': return in(t, float_set) ? pt::fixed : pt::none;
case 'G': upper = true; FMT_FALLTHROUGH;
case 'g': return in(t, float_set) ? pt::general : pt::none;
case 'A': upper = true; FMT_FALLTHROUGH;
case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c': return in(t, integral_set) ? pt::chr : pt::none;
case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default: return pt::none;
}
}
@ -415,7 +401,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
using iterator = basic_appender<Char>;
auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format);
auto parse_ctx = parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
@ -444,7 +430,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs();
specs.align = align::right;
specs.set_align(align::right);
// Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg);
@ -468,9 +454,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) {
if (specs.precision >= 0 && is_integral_type(arg.type())) {
// Ignore '0' for non-numeric types or if '-' present.
specs.fill = ' ';
specs.set_fill(' ');
}
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>());
@ -478,15 +464,16 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv);
arg = sv;
}
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false;
if (specs.fill.template get<Char>() == '0') {
if (arg.is_arithmetic() && specs.align != align::left)
specs.align = align::numeric;
else
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-'
// flag is also present.
if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
if (specs.fill_unit<Char>() == '0') {
if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
specs.set_align(align::numeric);
} else {
// Ignore '0' flag for non-numeric types or if '-' flag is also present.
specs.set_fill(' ');
}
}
// Parse length and convert the argument to the required type.
@ -511,44 +498,34 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
convert_arg<long>(arg, t);
}
break;
case 'j':
convert_arg<intmax_t>(arg, t);
break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'j': convert_arg<intmax_t>(arg, t); break;
case 'z': convert_arg<size_t>(arg, t); break;
case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
case 'L':
// printf produces garbage when 'L' is omitted for long double, no
// need to do the same.
break;
default:
--it;
convert_arg<void>(arg, c);
default: --it; convert_arg<void>(arg, c);
}
// Parse type.
if (it == end) report_error("invalid format string");
char type = static_cast<char>(*it++);
if (arg.is_integral()) {
if (is_integral_type(arg.type())) {
// Normalize type.
switch (type) {
case 'i':
case 'u':
type = 'd';
break;
case 'u': type = 'd'; break;
case 'c':
arg.visit(char_converter<basic_printf_context<Char>>(arg));
break;
}
}
bool upper = false;
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
if (specs.type == presentation_type::none)
specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
if (specs.type() == presentation_type::none)
report_error("invalid format specifier");
specs.upper = upper;
if (upper) specs.set_upper();
start = it;
@ -583,7 +560,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
return to_string(buf);
return {buf.data(), buf.size()};
}
/**
@ -594,7 +571,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
*
* std::string message = fmt::sprintf("The answer is %d", 42);
*/
template <typename S, typename... T, typename Char = char_t<S>>
template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
@ -619,7 +596,7 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
*
* fmt::fprintf(stderr, "Don't %s!", "panic");
*/
template <typename S, typename... T, typename Char = char_t<S>>
template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
make_printf_args<Char>(args...));

View file

@ -44,18 +44,6 @@ template <typename T> class is_set {
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
};
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
@ -76,9 +64,13 @@ struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
// Member function overloads.
template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
auto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {
return static_cast<T&&>(rng).begin();
}
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
auto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {
return static_cast<T&&>(rng).end();
}
// ADL overloads. Only participate in overload resolution if member functions
// are not found.
@ -115,17 +107,16 @@ struct has_mutable_begin_end<
// SFINAE properly unless there are distinct types
int>> : std::true_type {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
template <typename U, typename V = typename std::remove_cv<U>::type>
static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
template <typename> static void check(...);
public:
@ -266,12 +257,12 @@ template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>;
// These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs {
template <typename Char> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx);
detail::maybe_set_debug_format(f, true);
}
ParseContext& ctx;
parse_context<Char>& ctx;
};
template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type;
@ -327,11 +318,17 @@ struct formatter<Tuple, Char,
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin();
if (it != ctx.end() && *it != '}') report_error("invalid format specifier");
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
auto end = ctx.end();
if (it != end && detail::to_ascii(*it) == 'n') {
++it;
set_brackets({}, {});
set_separator({});
}
if (it != end && *it != '}') report_error("invalid format specifier");
ctx.advance_to(it);
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it;
}
@ -352,38 +349,17 @@ template <typename T, typename Char> struct is_range {
};
namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element>
using range_formatter_type =
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
.map(std::declval<Element>()))>,
Char>;
using range_formatter_type = formatter<remove_cvref_t<Element>, Char>;
template <typename R>
using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char>
struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail
template <typename...> struct conjunction : std::true_type {};
@ -415,7 +391,7 @@ struct range_formatter<
auto buf = basic_memory_buffer<Char>();
for (; it != end; ++it) buf.push_back(*it);
auto specs = format_specs();
specs.type = presentation_type::debug;
specs.set_type(presentation_type::debug);
return detail::write<Char>(
out, basic_string_view<Char>(buf.data(), buf.size()), specs);
}
@ -443,8 +419,7 @@ struct range_formatter<
closing_bracket_ = close;
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin();
auto end = ctx.end();
detail::maybe_set_debug_format(underlying_, true);
@ -486,7 +461,6 @@ struct range_formatter<
template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
auto mapper = detail::range_mapper<buffered_context<Char>>();
auto out = ctx.out();
auto it = detail::range_begin(range);
auto end = detail::range_end(range);
@ -498,7 +472,7 @@ struct range_formatter<
if (i > 0) out = detail::copy<Char>(separator_, out);
ctx.advance_to(out);
auto&& item = *it; // Need an lvalue
out = underlying_.format(mapper.map(item), ctx);
out = underlying_.format(item, ctx);
++i;
}
out = detail::copy<Char>(closing_bracket_, out);
@ -521,13 +495,8 @@ struct formatter<
range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string>
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
range_format_kind<R, Char>::value != range_format::debug_string>,
detail::is_formattable_delayed<R, Char>>::value>> {
private:
using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
@ -543,8 +512,7 @@ struct formatter<
detail::string_literal<Char, '}'>{});
}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return range_formatter_.parse(ctx);
}
@ -559,7 +527,9 @@ struct formatter<
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
private:
using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>;
@ -571,8 +541,7 @@ struct formatter<
public:
FMT_CONSTEXPR formatter() {}
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin();
auto end = ctx.end();
if (it != end) {
@ -586,7 +555,7 @@ struct formatter<
}
ctx.advance_to(it);
}
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx});
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it;
}
@ -596,12 +565,11 @@ struct formatter<
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
if (!no_delimiters_) out = detail::copy<Char>(open, out);
int i = 0;
auto mapper = detail::range_mapper<buffered_context<Char>>();
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
for (auto&& value : map) {
if (i > 0) out = detail::copy<Char>(sep, out);
ctx.advance_to(out);
detail::for_each2(formatters_, mapper.map(value),
detail::for_each2(formatters_, value,
detail::format_tuple_element<FormatContext>{
0, ctx, detail::string_literal<Char, ':', ' '>{}});
++i;
@ -631,8 +599,7 @@ struct formatter<
formatter<string_type, Char> underlying_;
public:
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return underlying_.parse(ctx);
}
@ -673,22 +640,22 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
#endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view_ref = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&,
join_view<It, Sentinel, Char>&&>;
using view = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>,
join_view<It, Sentinel, Char>>;
public:
using nonlocking = void;
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return value_formatter_.parse(ctx);
}
template <typename FormatContext>
auto format(view_ref& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto it = std::forward<view_ref>(value).begin;
auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
using iter =
conditional_t<std::is_copy_constructible<view>::value, It, It&>;
iter it = value.begin;
auto out = ctx.out();
if (it == value.end) return out;
out = value_formatter_.format(*it, ctx);
@ -703,39 +670,11 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
}
};
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
const Tuple& tuple;
basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {}
};
@ -746,37 +685,36 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, Tuple>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value,
auto format(const tuple_join_view<Char, Tuple>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
return do_format(value, ctx, std::tuple_size<Tuple>());
}
private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
template <typename ParseContext>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) {
-> const Char* {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
template <size_t N>
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
-> const Char* {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
@ -787,18 +725,20 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_)
.format(std::get<sizeof...(T) - N>(value.tuple), ctx);
using std::get;
auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out);
@ -846,6 +786,34 @@ struct formatter<
FMT_BEGIN_EXPORT
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
/**
* Returns an object that formats `std::tuple` with elements separated by `sep`.
*
@ -855,9 +823,9 @@ FMT_BEGIN_EXPORT
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename... T>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
-> tuple_join_view<char, T...> {
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> {
return {tuple, sep};
}

View file

@ -17,6 +17,7 @@
# include <complex>
# include <cstdlib>
# include <exception>
# include <functional>
# include <memory>
# include <thread>
# include <type_traits>
@ -26,7 +27,8 @@
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
# if FMT_HAS_INCLUDE(<filesystem>)
# if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# include <filesystem>
# endif
# if FMT_HAS_INCLUDE(<variant>)
@ -122,14 +124,16 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx);
Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
@ -145,8 +149,8 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
!path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_,
ctx);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
@ -180,7 +184,8 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private:
// Functor because C++11 doesn't support generic lambdas.
struct writer {
@ -200,7 +205,7 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return write_padded(ctx, writer{bs});
return this->write_padded(ctx, writer{bs});
}
};
@ -233,7 +238,7 @@ struct formatter<std::optional<T>, Char,
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
@ -277,10 +282,10 @@ FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char,
std::enable_if_t<is_formattable<T, Char>::value &&
std::enable_if_t<(std::is_void<T>::value ||
is_formattable<T, Char>::value) &&
is_formattable<E, Char>::value>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -291,7 +296,8 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
out = detail::write_escaped_alternative<Char>(out, *value);
if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value);
} else {
out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error());
@ -307,9 +313,7 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
return ctx.begin();
}
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const
@ -365,8 +369,7 @@ template <typename T, typename C> struct is_variant_formattable {
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -383,8 +386,7 @@ struct formatter<
Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -413,20 +415,37 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename Char> struct formatter<std::error_code, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
template <> struct formatter<std::error_code> {
private:
format_specs specs_;
detail::arg_ref<char> width_ref_;
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
return it;
}
template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto out = ctx.out();
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
FMT_CONSTEXPR20 auto format(const std::error_code& ec,
FormatContext& ctx) const -> decltype(ctx.out()) {
auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
memory_buffer buf;
buf.append(string_view(ec.category().name()));
buf.push_back(':');
detail::write<char>(appender(buf), ec.value());
return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),
specs);
}
};
@ -506,8 +525,7 @@ template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -528,8 +546,7 @@ struct formatter<
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
@ -643,7 +660,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
if (c.real() != 0) {
*out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.sign = sign::plus;
specs.set_sign(sign::plus);
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i');
@ -657,8 +674,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
}
public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value);
@ -668,12 +684,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = specs_;
if (specs.width_ref.kind != detail::arg_id_kind::none ||
specs.precision_ref.kind != detail::arg_id_kind::none) {
detail::handle_dynamic_spec<detail::width_checker>(specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(
specs.precision, specs.precision_ref, ctx);
if (specs.dynamic()) {
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
specs.width_ref, ctx);
detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
specs.precision_ref, ctx);
}
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
@ -681,12 +696,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs();
outer_specs.width = specs.width;
outer_specs.fill = specs.fill;
outer_specs.align = specs.align;
outer_specs.copy_fill_from(specs);
outer_specs.set_align(specs.align());
specs.width = 0;
specs.fill = {};
specs.align = align::none;
specs.set_fill({});
specs.set_align(align::none);
do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(),
@ -695,5 +710,17 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View file

@ -10,11 +10,12 @@
#include "color.h"
#include "format.h"
#include "ostream.h"
#include "ranges.h"
#ifndef FMT_MODULE
# include <cwchar>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# if FMT_USE_LOCALE
# include <locale>
# endif
#endif
@ -34,7 +35,8 @@ struct format_string_char<
};
template <typename S>
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> {
struct format_string_char<
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
using type = typename S::char_type;
};
@ -43,7 +45,7 @@ using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
#if FMT_USE_LOCALE
auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring();
@ -58,30 +60,64 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_parse_context = parse_context<wchar_t>;
using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using wformat_string = wstring_view;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
template <typename Char, typename... T> struct basic_fstring {
private:
basic_string_view<Char> str_;
static constexpr int num_static_named_args =
detail::count_static_named_args<T...>();
using checker = detail::format_string_checker<
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
num_static_named_args != detail::count_named_args<T...>()>;
using arg_pack = detail::arg_pack<T...>;
public:
using t = basic_fstring;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
if (FMT_USE_CONSTEVAL)
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
}
template <typename S,
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
std::is_same<typename S::char_type, Char>::value)>
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
FMT_CONSTEXPR int ignore =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
}
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
operator basic_string_view<Char>() const { return str_; }
auto get() const -> basic_string_view<Char> { return str_; }
};
template <typename Char, typename... T>
using basic_format_string = basic_fstring<Char, T...>;
template <typename... T>
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
#endif
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <>
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif
template <typename... T>
@ -90,14 +126,13 @@ constexpr auto make_wformat_args(T&... args)
return fmt::make_format_args<wformat_context>(args...);
}
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
return {s};
}
#endif
} // namespace literals
#endif
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
@ -105,9 +140,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep};
}
template <typename Range>
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
-> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
@ -118,19 +153,19 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep);
}
template <typename... T>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> {
return {tuple, sep};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> format_str,
auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, format_str, args);
return to_string(buf);
detail::vformat_to(buf, fmt, args);
return {buf.data(), buf.size()};
}
template <typename... T>
@ -151,8 +186,8 @@ template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str),
auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
@ -160,31 +195,33 @@ template <typename Locale, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& format_str,
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args);
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args,
detail::locale_ref(loc));
return {buf.data(), buf.size()};
}
template <typename Locale, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, T&&... args)
inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> {
return detail::vformat(
loc, detail::to_string_view(format_str),
fmt::make_format_args<buffered_context<Char>>(args...));
return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename S,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args);
detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out);
}
@ -203,37 +240,35 @@ template <typename Locale, typename S, typename OutputIt, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str,
inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(format_str), args,
detail::locale_ref(loc));
vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc));
return detail::get_iterator(buf, out);
}
template <typename OutputIt, typename Locale, typename S, typename... T,
template <typename Locale, typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
inline auto format_to(OutputIt out, const Locale& loc, const S& fmt,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(format_str),
return vformat_to(out, loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n,
basic_string_view<Char> format_str,
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
detail::vformat_to(buf, format_str, args);
detail::vformat_to(buf, fmt, args);
return {buf.out(), buf.count()};
}
@ -291,7 +326,7 @@ inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
-> std::wstring {
auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args);
return fmt::to_string(buf);
return {buf.data(), buf.size()};
}
template <typename... T>
@ -312,6 +347,22 @@ FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
return print(stdout, ts, fmt, args...);
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer);
}
template <typename... T>
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
template <typename... T>
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/// Converts `value` to `std::wstring` using the default format for type `T`.
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value);

View file

@ -1,5 +1,13 @@
module;
#define FMT_MODULE
#ifdef _MSVC_LANG
# define FMT_CPLUSPLUS _MSVC_LANG
#else
# define FMT_CPLUSPLUS __cplusplus
#endif
// Put all implementation-provided headers into the global module fragment
// to prevent attachment to this module.
#ifndef FMT_IMPORT_STD
@ -15,7 +23,9 @@ module;
# include <cstring>
# include <ctime>
# include <exception>
# include <expected>
# if FMT_CPLUSPLUS > 202002L
# include <expected>
# endif
# include <filesystem>
# include <fstream>
# include <functional>
@ -127,9 +137,17 @@ extern "C++" {
module :private;
#endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
extern "C++" {
#endif
#if FMT_HAS_INCLUDE("format.cc")
# include "format.cc"
#endif
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
# include "os.cc"
#endif
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
}
#endif

View file

@ -15,7 +15,8 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
#if FMT_USE_LOCALE
// DEPRECATED! locale_ref in the detail namespace
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
@ -26,8 +27,10 @@ template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);

View file

@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
}
void report_windows_error(int error_code, const char* message) noexcept {
report_error(detail::format_windows_error, error_code, message);
do_report_error(detail::format_windows_error, error_code, message);
}
#endif // _WIN32
@ -374,30 +374,25 @@ long getpagesize() {
}
# endif
namespace detail {
void file_buffer::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<file_buffer&>(buf).flush();
void ostream::grow(buffer<char>& buf, size_t) {
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
}
file_buffer::file_buffer(cstring_view path, const ostream_params& params)
ostream::ostream(cstring_view path, const detail::ostream_params& params)
: buffer<char>(grow), file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
file_buffer::file_buffer(file_buffer&& other) noexcept
ostream::ostream(ostream&& other) noexcept
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.clear();
other.set(nullptr, 0);
}
file_buffer::~file_buffer() {
ostream::~ostream() {
flush();
delete[] data();
}
} // namespace detail
ostream::~ostream() = default;
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

View file

@ -29,3 +29,5 @@
#define ICON_EMOJI_UNLOCKED "\xf0\x9f\x94\x93"
#define ICON_EMOJI_REFRESH "\xf0\x9f\x94\x84"
#define ICON_EMOJI_PROHIBITED "\xf0\x9f\x9a\xab"
#define ICON_EMOJI_CALENDAR "\xF0\x9F\x93\x85"
#define ICON_EMOJI_HOURGLASS "\xe2\x8f\xb3"

View file

@ -10957,7 +10957,7 @@ void ImGui::SetScrollY(float scroll_y)
void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)
{
IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
window->ScrollTarget.x = IM_TRUNC(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 + window->Scroll.x); // Convert local position to scroll offset
window->ScrollTarget.x = IM_TRUNC(local_x - window->DecoOuterSizeX1 - window->DecoInnerSizeX1 + window->ScrollExpected.x); // Convert local position to scroll offset
window->ScrollTargetCenterRatio.x = center_x_ratio;
window->ScrollTargetEdgeSnapDist.x = 0.0f;
}
@ -10965,7 +10965,7 @@ void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x
void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)
{
IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
window->ScrollTarget.y = IM_TRUNC(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 + window->Scroll.y); // Convert local position to scroll offset
window->ScrollTarget.y = IM_TRUNC(local_y - window->DecoOuterSizeY1 - window->DecoInnerSizeY1 + window->ScrollExpected.y); // Convert local position to scroll offset
window->ScrollTargetCenterRatio.y = center_y_ratio;
window->ScrollTargetEdgeSnapDist.y = 0.0f;
}
@ -12959,6 +12959,7 @@ static void ImGui::NavUpdateCreateWrappingRequest()
bb_rel.TranslateX(-bb_rel.GetWidth()); // Previous column
clip_dir = ImGuiDir_Left;
}
SetScrollY(window, window->ScrollMax.y);
do_forward = true;
}
if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
@ -12969,6 +12970,7 @@ static void ImGui::NavUpdateCreateWrappingRequest()
bb_rel.TranslateX(+bb_rel.GetWidth()); // Next column
clip_dir = ImGuiDir_Right;
}
SetScrollY(window, 0.0f);
do_forward = true;
}
if (!do_forward)

View file

@ -141,6 +141,11 @@ RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_asy
*/
RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size);
/**
* Returns true if any achievement submissions have failed and are currently pending.
*/
RC_EXPORT int RC_CCONV rc_client_is_disconnected(rc_client_t* client);
/*****************************************************************************\
| Logging |
\*****************************************************************************/
@ -503,6 +508,11 @@ RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement
*/
RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client);
/**
* Returns the number of outstanding achievement unlocks.
*/
RC_EXPORT int RC_CCONV rc_client_get_award_achievement_pending_count(rc_client_t* client);
/*****************************************************************************\
| Leaderboards |
\*****************************************************************************/
@ -586,7 +596,7 @@ RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard
/**
* Returns non-zero if the current game has any leaderboards.
*/
RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client);
RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client, int include_hidden);
typedef struct rc_client_leaderboard_entry_t {
const char* user;

View file

@ -3941,6 +3941,20 @@ static int rc_client_is_award_achievement_pending(const rc_client_t* client, uin
return 0;
}
int rc_client_get_award_achievement_pending_count(rc_client_t* client)
{
/* assume lock already held */
int count = 0;
rc_client_scheduled_callback_data_t* scheduled_callback = client->state.scheduled_callbacks;
for (; scheduled_callback; scheduled_callback = scheduled_callback->next)
{
if (scheduled_callback->callback == rc_client_award_achievement_retry)
count++;
}
return count;
}
static void rc_client_award_achievement_server_call(rc_client_award_achievement_callback_data_t* ach_data);
static void rc_client_award_achievement_retry(rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now)
@ -4451,10 +4465,10 @@ void rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list)
free(list);
}
int rc_client_has_leaderboards(rc_client_t* client)
int rc_client_has_leaderboards(rc_client_t* client, int include_hidden)
{
rc_client_subset_info_t* subset;
int result;
int i, result;
if (!client)
return 0;
@ -4477,8 +4491,20 @@ int rc_client_has_leaderboards(rc_client_t* client)
continue;
if (subset->public_.num_leaderboards > 0) {
result = 1;
break;
if (!include_hidden) {
for (i = 0; i < subset->public_.num_leaderboards; i++) {
if (subset->leaderboards[i].hidden)
continue;
result = 1;
break;
}
if (result)
break;
} else {
result = 1;
break;
}
}
}
@ -6411,3 +6437,8 @@ size_t rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_
buffer[buffer_size - 1] = '\0';
return result;
}
int rc_client_is_disconnected(rc_client_t* client)
{
return (client && (client->state.disconnect & (RC_CLIENT_DISCONNECT_VISIBLE | RC_CLIENT_DISCONNECT_SHOW_PENDING) != 0));
}

View file

@ -108,14 +108,14 @@ LIBJPEGTURBO=3.1.0
LIBPNG=1.6.45
LIBWEBP=1.5.0
LIBZIP=1.11.3
SDL3=3.2.4
SDL3=3.2.8
QT=6.8.2
ZLIBNG=2.2.4
ZSTD=1.5.6
CPUINFO=3ebbfd45645650c4940bf0f3b4d25ab913466bb0
DISCORD_RPC=144f3a3f1209994d8d9e8a87964a989cb9911c1e
LUNASVG=9af1ac7b90658a279b372add52d6f77a4ebb482c
DISCORD_RPC=cc59d26d1d628fbd6527aac0ac1d6301f4978b92
LUNASVG=4a1c98ccb1da8a5a92ddc4f97339869b1ae556f4
SHADERC=fc65b19d2098cf81e55b4edc10adad2ad8268361
SOUNDTOUCH=463ade388f3a51da078dc9ed062bf28e4ba29da7
SPIRV_CROSS=vulkan-sdk-1.4.304.0
@ -137,10 +137,10 @@ fi
cat > SHASUMS <<EOF
baf8aebd22002b762d803ba0e1e389b6b4415159334e9d34bba1a938f6de8ce6 libbacktrace-$LIBBACKTRACE.tar.gz
2938328317301dfbe30176d79c251733aa5e7ec5c436c800b99ed4da7adcb0f0 SDL3-$SDL3.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 SDL3-$SDL3.tar.gz
b60832071919220d2fe692151fb420fa9ea489aa4c7a2eb0e01c830cbe469858 cpuinfo-$CPUINFO.tar.gz
3eea5ccce6670c126282f1ba4d32c19d486db49a1a5cbfb8d6f48774784d310c discord-rpc-$DISCORD_RPC.tar.gz
3998b024b0d442614a9ee270e76e018bb37a17b8c6941212171731123cbbcac7 lunasvg-$LUNASVG.tar.gz
297cd48a287a9113eec44902574084c6ab3b6a8b28d02606765a7fded431d7d8 discord-rpc-$DISCORD_RPC.tar.gz
5fe7abc6c4601f21fa56ffbf12507e80684942c3134b7888701ede836e6287e2 lunasvg-$LUNASVG.tar.gz
d1ef912c27e06307f2b2a5b6386070d0b8fae2bb5851f50841df7b73dcf5abdf shaderc-$SHADERC.tar.gz
fe45c2af99f6102d2704277d392c1c83b55180a70bfd17fb888cc84a54b70573 soundtouch-$SOUNDTOUCH.tar.gz
EOF
@ -274,16 +274,17 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
EOF
# Build zlib first because of the things that depend on it.
if [ "$SKIP_ZLIBNG" != true ]; then
echo "Building zlib-ng..."
rm -fr "zlib-ng-$ZLIBNG"
tar xf "zlib-ng-$ZLIBNG.tar.gz"
cd "zlib-ng-$ZLIBNG"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DZLIB_COMPAT=ON -DZLIBNG_ENABLE_TESTS=OFF -DZLIB_ENABLE_TESTS=OFF -DWITH_GTEST=OFF -B build -G Ninja
cmake --build build --parallel
ninja -C build install
cd ..
fi
# Disabled because it currently causes crashes on armhf.
#if [ "$SKIP_ZLIBNG" != true ]; then
# echo "Building zlib-ng..."
# rm -fr "zlib-ng-$ZLIBNG"
# tar xf "zlib-ng-$ZLIBNG.tar.gz"
# cd "zlib-ng-$ZLIBNG"
# cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DZLIB_COMPAT=ON -DZLIBNG_ENABLE_TESTS=OFF -DZLIB_ENABLE_TESTS=OFF -DWITH_GTEST=OFF -B build -G Ninja
# cmake --build build --parallel
# ninja -C build install
# cd ..
#fi
# NOTE: Must be a shared library because otherwise aarch64 libgcc symbols are missing when building with clang.
echo "Building libbacktrace..."
@ -390,7 +391,6 @@ echo "Building SDL..."
rm -fr "SDL3-$SDL3"
tar xf "SDL3-$SDL3.tar.gz"
cd "SDL3-$SDL3"
patch -p1 < "$SCRIPTDIR/sdl3-joystick-crash.patch"
cmake -B build "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -G Ninja
cmake --build build --parallel
ninja -C build install

View file

@ -76,14 +76,14 @@ LIBJPEGTURBO=3.1.0
LIBPNG=1.6.45
LIBWEBP=1.5.0
LIBZIP=1.11.3
SDL3=3.2.4
SDL3=3.2.8
QT=6.8.2
ZLIBNG=2.2.4
ZSTD=1.5.6
CPUINFO=3ebbfd45645650c4940bf0f3b4d25ab913466bb0
DISCORD_RPC=144f3a3f1209994d8d9e8a87964a989cb9911c1e
LUNASVG=9af1ac7b90658a279b372add52d6f77a4ebb482c
DISCORD_RPC=cc59d26d1d628fbd6527aac0ac1d6301f4978b92
LUNASVG=4a1c98ccb1da8a5a92ddc4f97339869b1ae556f4
SHADERC=fc65b19d2098cf81e55b4edc10adad2ad8268361
SOUNDTOUCH=463ade388f3a51da078dc9ed062bf28e4ba29da7
SPIRV_CROSS=vulkan-sdk-1.4.304.0
@ -104,10 +104,10 @@ fi
cat > SHASUMS <<EOF
baf8aebd22002b762d803ba0e1e389b6b4415159334e9d34bba1a938f6de8ce6 libbacktrace-$LIBBACKTRACE.tar.gz
2938328317301dfbe30176d79c251733aa5e7ec5c436c800b99ed4da7adcb0f0 SDL3-$SDL3.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 SDL3-$SDL3.tar.gz
b60832071919220d2fe692151fb420fa9ea489aa4c7a2eb0e01c830cbe469858 cpuinfo-$CPUINFO.tar.gz
3eea5ccce6670c126282f1ba4d32c19d486db49a1a5cbfb8d6f48774784d310c discord-rpc-$DISCORD_RPC.tar.gz
3998b024b0d442614a9ee270e76e018bb37a17b8c6941212171731123cbbcac7 lunasvg-$LUNASVG.tar.gz
297cd48a287a9113eec44902574084c6ab3b6a8b28d02606765a7fded431d7d8 discord-rpc-$DISCORD_RPC.tar.gz
5fe7abc6c4601f21fa56ffbf12507e80684942c3134b7888701ede836e6287e2 lunasvg-$LUNASVG.tar.gz
d1ef912c27e06307f2b2a5b6386070d0b8fae2bb5851f50841df7b73dcf5abdf shaderc-$SHADERC.tar.gz
fe45c2af99f6102d2704277d392c1c83b55180a70bfd17fb888cc84a54b70573 soundtouch-$SOUNDTOUCH.tar.gz
EOF
@ -324,7 +324,6 @@ echo "Building SDL..."
rm -fr "SDL3-$SDL3"
tar xf "SDL3-$SDL3.tar.gz"
cd "SDL3-$SDL3"
patch -p1 < "$SCRIPTDIR/sdl3-joystick-crash.patch"
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -G Ninja
cmake --build build --parallel
ninja -C build install

View file

@ -38,7 +38,7 @@ fi
FREETYPE=2.13.3
HARFBUZZ=10.2.0
SDL3=3.2.4
SDL3=3.2.8
ZSTD=1.5.6
LIBPNG=1.6.45
LIBJPEGTURBO=3.1.0
@ -86,7 +86,7 @@ cat > SHASUMS <<EOF
9564c72b1dfd1d6fe6274c5f95a8d989b59854575d4bbee44ade7bc17aa9bc93 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
7d6fab70cf844bf6769077bd5d7a74893f8ffd4dfb42861745750c63c2a5c92c libwebp-$LIBWEBP.tar.gz
9509d878ba788271c8b5abca9cfde1720f075335686237b7e9a9e7210fe67c1b libzip-$LIBZIP.tar.xz
2938328317301dfbe30176d79c251733aa5e7ec5c436c800b99ed4da7adcb0f0 SDL3-$SDL3.tar.gz
13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03 SDL3-$SDL3.tar.gz
8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1 zstd-$ZSTD.tar.gz
40973d44970dbc83ef302b0609f2e74982be2d85916dd2ee7472d30678a7abe6 ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
@ -226,7 +226,6 @@ echo "Installing SDL..."
rm -fr "SDL3-$SDL3"
tar xf "SDL3-$SDL3.tar.gz"
cd "SDL3-$SDL3"
patch -p1 < "$SCRIPTDIR/sdl3-joystick-crash.patch"
cmake -B build "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_UNIVERSAL" -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -DSDL_X11=OFF -DBUILD_SHARED_LIBS=ON
make -C build "-j$NPROCS"
make -C build install

View file

@ -51,7 +51,7 @@ set LIBJPEGTURBO=3.1.0
set LIBPNG=1645
set QT=6.8.2
set QTMINOR=6.8
set SDL3=3.2.4
set SDL3=3.2.8
set WEBP=1.5.0
set LIBZIP=1.11.3
set ZLIBNG=2.2.4
@ -70,7 +70,7 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" "https://download.savannah.gnu.o
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip" 180cec309e817ec50953cd4d4208025c1931203231cf455418b0dd4021091951 || goto error
call :downloadfile "lpng%LIBPNG%.zip" "https://download.sourceforge.net/libpng/lpng%LIBPNG%.zip" a66c4b1350b67776e90263e2550933067cd9ccbd318db489f84dcc0d2b033249 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 9564c72b1dfd1d6fe6274c5f95a8d989b59854575d4bbee44ade7bc17aa9bc93 || goto error
call :downloadfile "SDL3-%SDL3%.zip" "https://github.com/libsdl-org/SDL/releases/download/release-%SDL3%/SDL3-%SDL3%.zip" 369c242d44ba0647873a8981cb071ee82c51d45f91f449bee97171bfad98422b || goto error
call :downloadfile "SDL3-%SDL3%.zip" "https://github.com/libsdl-org/SDL/releases/download/release-%SDL3%/SDL3-%SDL3%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
@ -190,7 +190,6 @@ echo Building SDL...
rmdir /S /Q "SDL3-%SDL3%"
%SEVENZIP% x "SDL3-%SDL3%.zip" || goto error
cd "SDL3-%SDL3%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\sdl3-joystick-crash.patch" || goto error
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error

View file

@ -49,7 +49,7 @@ set LIBJPEGTURBO=3.1.0
set LIBPNG=1645
set QT=6.8.2
set QTMINOR=6.8
set SDL3=3.2.4
set SDL3=3.2.8
set WEBP=1.5.0
set LIBZIP=1.11.3
set ZLIBNG=2.2.4
@ -68,7 +68,7 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" "https://download.savannah.gnu.o
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip" 180cec309e817ec50953cd4d4208025c1931203231cf455418b0dd4021091951 || goto error
call :downloadfile "lpng%LIBPNG%.zip" "https://download.sourceforge.net/libpng/lpng%LIBPNG%.zip" a66c4b1350b67776e90263e2550933067cd9ccbd318db489f84dcc0d2b033249 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 9564c72b1dfd1d6fe6274c5f95a8d989b59854575d4bbee44ade7bc17aa9bc93 || goto error
call :downloadfile "SDL3-%SDL3%.zip" "https://github.com/libsdl-org/SDL/releases/download/release-%SDL3%/SDL3-%SDL3%.zip" 369c242d44ba0647873a8981cb071ee82c51d45f91f449bee97171bfad98422b || goto error
call :downloadfile "SDL3-%SDL3%.zip" "https://github.com/libsdl-org/SDL/releases/download/release-%SDL3%/SDL3-%SDL3%.zip" 7f8ff5c8246db4145301bc122601a5f8cef25ee2c326eddb3e88668849c61ddf || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 44087aec0caa4aa81437e787917d29d97536484a682a5d51ec035878e57c0b5c || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 83c72b5dfad04854acf61d592e3f9cdc2ed894779aab8d0470d966715266caaf || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 144d55e4d199793a76c53f19872633a79aec0314039f6f99b6a10b5be7a78fbf || goto error
@ -187,7 +187,6 @@ echo Building SDL...
rmdir /S /Q "SDL3-%SDL3%"
%SEVENZIP% x "SDL3-%SDL3%.zip" || goto error
cd "SDL3-%SDL3%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\sdl3-joystick-crash.patch" || goto error
cmake -B build -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -DSDL_TESTS=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error

View file

@ -1,28 +0,0 @@
From d90c0e7fefbbf9aa7061884dba02d8c809a30a10 Mon Sep 17 00:00:00 2001
From: Stenzek <stenzek@gmail.com>
Date: Mon, 10 Feb 2025 22:52:50 +1000
Subject: [PATCH] joystick: Fix PS5 player LED hint change callback name
The LED hint was getting registered for SDL_HINT_JOYSTICK_ENHANCED_REPORTS
instead of SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, which results in a
use-after-free followed by a crash.
---
src/joystick/hidapi/SDL_hidapi_ps5.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 94b720d42..abf59a87f 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -952,7 +952,7 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
SDL_PS5EnhancedReportsChanged, ctx);
- SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
+ SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
SDL_PS5PlayerLEDHintChanged, ctx);
return true;
--
2.48.1

View file

@ -107,7 +107,6 @@ declare -a DEPLIBS=(
"libwebpdemux.so.2"
"libwebpmux.so.3"
"libwebp.so.7"
"libz.so.1"
"libzip.so.5"
"libzstd.so.1"

View file

@ -15,10 +15,8 @@ build-options:
no-debuginfo: true
sources:
- type: archive
url: "https://github.com/libsdl-org/SDL/releases/download/release-3.2.4/SDL3-3.2.4.tar.gz"
sha256: "2938328317301dfbe30176d79c251733aa5e7ec5c436c800b99ed4da7adcb0f0"
- type: patch
path: "../../../deps/sdl3-joystick-crash.patch"
url: "https://github.com/libsdl-org/SDL/releases/download/release-3.2.8/SDL3-3.2.8.tar.gz"
sha256: "13388fabb361de768ecdf2b65e52bb27d1054cae6ccb6942ba926e378e00db03"
cleanup:
- /bin
- /include

View file

@ -10,6 +10,44 @@
<description>
<p>DuckStation is an simulator/emulator of the Sony PlayStation(TM) console, focusing on playability, speed, and long-term maintainability. The goal is to be as accurate as possible while maintaining performance suitable for low-end devices.</p>
<p>"Hack" options are discouraged, the default configuration should support all playable games with only some of the enhancements having compatibility issues.</p>
<p>DuckStation features a fully-featured frontend built using Qt, as well as a fullscreen/TV UI based on Dear ImGui. Other features include:</p>
<ul>
<li>Hardware renderer supporting OpenGL and Vulkan APIs.</li>
<li>Upscaling, texture filtering, and true colour (24-bit) in hardware renderers.</li>
<li>PGXP for geometry precision, texture correction, and depth buffer emulation.</li>
<li>Accurate blending via Rasterizer Order Views/Fragment Shader Interlock.</li>
<li>Texture replacement system in hardware renderers.</li>
<li>Vectorized and multi-threaded software renderer.</li>
<li>Motion adaptive deinterlacing.</li>
<li>Adaptive downsampling filter.</li>
<li>Screen rotation for vertical or "TATE" shmup games.</li>
<li>Post processing shader chains (GLSL and Reshade FX).</li>
<li>Border overlays/bezels displayed around game content.</li>
<li>"Fast boot" for skipping BIOS splash/intro.</li>
<li>Save state support, with runahead and rewind.</li>
<li>Supports reading directly from CD, bin/cue images, raw bin/img files, MAME CHD, single-track ECM, MDS/MDF, and unencrypted PBP formats.</li>
<li>Preloading of disc images to RAM to avoid disk sleeping hitches.</li>
<li>Merging of multi-disc games in game list/grid with memory cards shared between discs.</li>
<li>Automatic loading/applying of PPF patches.</li>
<li>Direct booting of homebrew executables.</li>
<li>Direct loading of Portable Sound Format (psf) files.</li>
<li>Time stretched audio when running outside of 100% speed.</li>
<li>Digital and analog controllers for input (rumble is forwarded to host).</li>
<li>GunCon and Justifier lightgun support (simulated with mouse).</li>
<li>NeGcon support.</li>
<li>Controller presets and per-game configuration.</li>
<li>Automatic content scanning - game titles/hashes are provided by redump.org.</li>
<li>Optional automatic switching of memory cards for each game.</li>
<li>Supports loading cheats from existing lists.</li>
<li>Memory card editor and save importer.</li>
<li>Emulated CPU overclocking.</li>
<li>Integrated and remote debugging.</li>
<li>Multitap controllers (up to 8 devices).</li>
<li>RetroAchievements.</li>
<li>Video capture with FFmpeg backends.</li>
<li>Free camera function.</li>
<li>Parallel port cartridge emulation.</li>
</ul>
<p>"PlayStation" and "PSX" are registered trademarks of Sony Interactive Entertainment Europe Limited. This project is not affiliated in any way with Sony Interactive Entertainment.</p>
</description>
<content_rating type="oars-1.1"/>
@ -19,13 +57,21 @@
<url type="vcs-browser">https://github.com/stenzek/duckstation</url>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/main-qt.png</image>
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/linux-main-qt.png</image>
<caption>Desktop Interface</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/bigduck.png</image>
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/linux-bigpicture.png</image>
<caption>Big Picture Mode Interface</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/linux-hover.png</image>
<caption>Hover Racing Game</caption>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/stenzek/duckstation/md-images/linux-fromage.png</image>
<caption>Fromage Game</caption>
</screenshot>
</screenshots>
<releases>
<release version="@GIT_VERSION@" date="@GIT_DATE@" />

View file

@ -289,7 +289,7 @@ BinaryFileReader& BinaryFileReader::operator=(BinaryFileReader&& move)
bool BinaryFileReader::IsAtEnd()
{
return (FileSystem::FTell64(m_fp) == m_size);
return (!m_fp || FileSystem::FTell64(m_fp) == m_size);
}
bool BinaryFileReader::ReadCString(std::string* dst)

View file

@ -23,7 +23,7 @@
X(FileLoader) \
X(FileSystem) \
X(FullscreenUI) \
X(GDBProtocol) \
X(GDBServer) \
X(GPU) \
X(GPUDevice) \
X(GPUDump) \

View file

@ -415,6 +415,19 @@ void ReplaceAll(std::string* subject, const char search, const char replacement)
/// Parses an assignment string (Key = Value) into its two components.
bool ParseAssignmentString(const std::string_view str, std::string_view* key, std::string_view* value);
/// Helper for tokenizing strings.
ALWAYS_INLINE std::optional<std::string_view> GetNextToken(std::string_view& caret, char separator)
{
std::optional<std::string_view> ret;
const std::string_view::size_type pos = caret.find(separator);
if (pos != std::string_view::npos)
{
ret = caret.substr(0, pos);
caret = caret.substr(pos + 1);
}
return ret;
}
/// Unicode replacement character.
static constexpr char32_t UNICODE_REPLACEMENT_CHARACTER = 0xFFFD;

View file

@ -90,7 +90,7 @@ static constexpr float LEADERBOARD_STARTED_NOTIFICATION_TIME = 3.0f;
static constexpr float LEADERBOARD_FAILED_NOTIFICATION_TIME = 3.0f;
static constexpr float INDICATOR_FADE_IN_TIME = 0.1f;
static constexpr float INDICATOR_FADE_OUT_TIME = 0.5f;
static constexpr float INDICATOR_FADE_OUT_TIME = 0.3f;
// Some API calls are really slow. Set a longer timeout.
static constexpr float SERVER_CALL_TIMEOUT = 60.0f;
@ -155,6 +155,7 @@ static void BeginChangeDisc();
static void UpdateGameSummary(bool update_progress_database, bool force_update_progress_database);
static std::string GetLocalImagePath(const std::string_view image_name, int type);
static void DownloadImage(std::string url, std::string cache_path);
static const std::string& GetCachedAchievementBadgePath(const rc_client_achievement_t* achievement, int state);
static void UpdateGlyphRanges();
static TinyString DecryptLoginToken(std::string_view encrypted_token, std::string_view username);
@ -204,8 +205,9 @@ static void LeaderboardFetchAllCallback(int result, const char* error_message, r
#ifndef __ANDROID__
static void DrawAchievement(const rc_client_achievement_t* cheevo);
static void DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard);
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, bool is_self, float rank_column_width,
float name_column_width, float time_column_width, float column_spacing);
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
float rank_column_width, float name_column_width, float time_column_width,
float column_spacing);
#endif
static std::string GetHashDatabasePath();
@ -266,7 +268,10 @@ struct State
rc_client_async_handle_t* load_game_request = nullptr;
rc_client_achievement_list_t* achievement_list = nullptr;
std::vector<std::pair<const void*, std::string>> achievement_badge_paths;
std::vector<std::tuple<const void*, int, std::string>> achievement_badge_paths;
const rc_client_achievement_t* most_recent_unlock = nullptr;
const rc_client_achievement_t* achievement_nearest_completion = nullptr;
rc_client_leaderboard_list_t* leaderboard_list = nullptr;
const rc_client_leaderboard_t* open_leaderboard = nullptr;
@ -511,7 +516,7 @@ void Achievements::UpdateGlyphRanges()
}
}
if (rc_client_has_leaderboards(s_state.client))
if (rc_client_has_leaderboards(s_state.client, false))
{
rc_client_leaderboard_list_t* const leaderboards =
rc_client_create_leaderboard_list(s_state.client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
@ -603,6 +608,11 @@ const std::string& Achievements::GetGameTitle()
return s_state.game_title;
}
const std::string& Achievements::GetGamePath()
{
return s_state.game_path;
}
const std::string& Achievements::GetGameIconPath()
{
return s_state.game_icon;
@ -1027,6 +1037,49 @@ void Achievements::UpdateGameSummary(bool update_progress_database, bool force_u
UpdateProgressDatabase(force_update_progress_database);
}
void Achievements::UpdateRecentUnlockAndAlmostThere()
{
const auto lock = GetLock();
if (!HasActiveGame())
return;
s_state.most_recent_unlock = nullptr;
s_state.achievement_nearest_completion = nullptr;
rc_client_achievement_list_t* const achievements = rc_client_create_achievement_list(
s_state.client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS);
if (!achievements)
return;
for (u32 i = 0; i < achievements->num_buckets; i++)
{
const rc_client_achievement_bucket_t& bucket = achievements->buckets[i];
for (u32 j = 0; j < bucket.num_achievements; j++)
{
const rc_client_achievement_t* achievement = bucket.achievements[j];
if (achievement->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED)
{
if (!s_state.most_recent_unlock || achievement->unlock_time > s_state.most_recent_unlock->unlock_time)
s_state.most_recent_unlock = achievement;
}
else
{
// find the achievement with the greatest normalized progress, but skip anything below 80%,
// matching the rc_client definition of "almost there"
const float percent_cutoff = 80.0f;
if (achievement->measured_percent >= percent_cutoff &&
(!s_state.achievement_nearest_completion ||
achievement->measured_percent > s_state.achievement_nearest_completion->measured_percent))
{
s_state.achievement_nearest_completion = achievement;
}
}
}
}
rc_client_destroy_achievement_list(achievements);
}
void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lock)
{
// Limit rich presence updates to once per second, since it could change per frame.
@ -1252,7 +1305,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
}
const bool has_achievements = rc_client_has_achievements(client);
const bool has_leaderboards = rc_client_has_leaderboards(client);
const bool has_leaderboards = rc_client_has_leaderboards(client, false);
// Only display summary if the game title has changed across discs.
const bool display_summary = (s_state.game_id != info->id || s_state.game_title != info->title);
@ -1968,6 +2021,18 @@ std::string Achievements::GetAchievementBadgePath(const rc_client_achievement_t*
return path;
}
const std::string& Achievements::GetCachedAchievementBadgePath(const rc_client_achievement_t* achievement, int state)
{
for (const auto& [l_cheevo, l_state, l_path] : s_state.achievement_badge_paths)
{
if (l_cheevo == achievement && l_state == state)
return l_path;
}
std::string path = GetAchievementBadgePath(achievement, state);
return std::get<2>(s_state.achievement_badge_paths.emplace_back(achievement, state, std::move(path)));
}
std::string Achievements::GetLeaderboardUserBadgePath(const rc_client_leaderboard_entry_t* entry)
{
// TODO: maybe we should just cache these in memory...
@ -1993,7 +2058,8 @@ bool Achievements::IsLoggedInOrLoggingIn()
bool Achievements::CanEnableHardcoreMode()
{
return (s_state.load_game_request || s_state.has_achievements || s_state.has_leaderboards);
// have to re-query leaderboards because hidden should still trip HC
return (s_state.load_game_request || s_state.has_achievements || rc_client_has_leaderboards(s_state.client, true));
}
bool Achievements::Login(const char* username, const char* password, Error* error)
@ -2309,6 +2375,9 @@ void Achievements::ClearUIState()
rc_client_destroy_achievement_list(s_state.achievement_list);
s_state.achievement_list = nullptr;
}
s_state.most_recent_unlock = nullptr;
s_state.achievement_nearest_completion = nullptr;
}
template<typename T>
@ -2335,6 +2404,8 @@ static float IndicatorOpacity(float delta_time, T& i)
void Achievements::DrawGameOverlays()
{
using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::ModAlpha;
using ImGuiFullscreen::RenderShadowedTextClipped;
using ImGuiFullscreen::UIStyle;
if (!HasActiveGame() || !g_settings.achievements_overlays)
@ -2342,10 +2413,13 @@ void Achievements::DrawGameOverlays()
const auto lock = GetLock();
constexpr float bg_opacity = 0.8f;
const float margin =
std::max(ImCeil(ImGuiManager::GetScreenMargin() * ImGuiManager::GetGlobalScale()), LayoutScale(10.0f));
const float spacing = LayoutScale(10.0f);
const float padding = LayoutScale(10.0f);
const float rounding = LayoutScale(10.0f);
const ImVec2 image_size =
LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT);
const ImGuiIO& io = ImGui::GetIO();
@ -2361,13 +2435,12 @@ void Achievements::DrawGameOverlays()
{
AchievementChallengeIndicator& indicator = *it;
const float opacity = IndicatorOpacity(io.DeltaTime, indicator);
const u32 col = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity));
GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(indicator.badge_path);
if (badge)
{
dl->AddImage(badge, current_position, current_position + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),
col);
ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity)));
current_position.x -= x_advance;
}
@ -2389,7 +2462,6 @@ void Achievements::DrawGameOverlays()
{
AchievementProgressIndicator& indicator = s_state.active_progress_indicator.value();
const float opacity = IndicatorOpacity(io.DeltaTime, indicator);
const u32 col = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity));
const char* text_start = s_state.active_progress_indicator->achievement->measured_progress;
const char* text_end = text_start + std::strlen(text_start);
@ -2399,23 +2471,24 @@ void Achievements::DrawGameOverlays()
const ImVec2 box_min = ImVec2(position.x - image_size.x - text_size.x - spacing - padding * 2.0f,
position.y - image_size.y - padding * 2.0f);
const ImVec2 box_max = position;
const float box_rounding = LayoutScale(1.0f);
dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ImVec4(0.13f, 0.13f, 0.13f, opacity * 0.5f)), box_rounding);
dl->AddRect(box_min, box_max, ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.8f, opacity)), box_rounding);
dl->AddRectFilled(box_min, box_max,
ImGui::GetColorU32(ModAlpha(UIStyle.ToastBackgroundColor, opacity * bg_opacity)), rounding);
GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(indicator.badge_path);
if (badge)
{
const ImVec2 badge_pos = box_min + ImVec2(padding, padding);
dl->AddImage(badge, badge_pos, badge_pos + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
dl->AddImage(badge, badge_pos, badge_pos + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),
ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity)));
}
const ImVec2 text_pos =
box_min + ImVec2(padding + image_size.x + spacing, (box_max.y - box_min.y - text_size.y) * 0.5f);
const ImVec4 text_clip_rect(text_pos.x, text_pos.y, box_max.x, box_max.y);
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, col, text_start, text_end, 0.0f,
&text_clip_rect);
const ImRect text_clip_rect(text_pos, box_max);
RenderShadowedTextClipped(dl, UIStyle.MediumFont, text_pos, box_max,
ImGui::GetColorU32(ModAlpha(UIStyle.ToastTextColor, opacity)), text_start, text_end,
&text_size, ImVec2(0.0f, 0.0f), 0.0f, &text_clip_rect);
if (!indicator.active && opacity <= 0.01f)
{
@ -2440,25 +2513,22 @@ void Achievements::DrawGameOverlays()
const ImVec2 size = ImGuiFullscreen::UIStyle.MediumFont->CalcTextSizeA(
ImGuiFullscreen::UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, width_string.c_str(), width_string.end_ptr());
const ImVec2 box_min = ImVec2(position.x - size.x - padding * 2.0f, position.y - size.y - padding * 2.0f);
const ImVec2 box_max = position;
const float box_rounding = LayoutScale(1.0f);
dl->AddRectFilled(box_min, box_max, ImGui::GetColorU32(ImVec4(0.13f, 0.13f, 0.13f, opacity * 0.5f)),
box_rounding);
dl->AddRect(box_min, box_max, ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.8f, opacity)), box_rounding);
const ImRect box(ImVec2(position.x - size.x - padding * 2.0f, position.y - size.y - padding * 2.0f), position);
dl->AddRectFilled(box.Min, box.Max,
ImGui::GetColorU32(ModAlpha(UIStyle.ToastBackgroundColor, opacity * bg_opacity)), rounding);
const u32 text_col = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity));
const u32 text_col = ImGui::GetColorU32(ModAlpha(UIStyle.ToastTextColor, opacity));
const ImVec2 text_size = ImGuiFullscreen::UIStyle.MediumFont->CalcTextSizeA(
ImGuiFullscreen::UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, indicator.text.c_str(),
indicator.text.c_str() + indicator.text.length());
const ImVec2 text_pos = ImVec2(box_max.x - padding - text_size.x, box_min.y + padding);
const ImVec4 text_clip_rect(box_min.x, box_min.y, box_max.x, box_max.y);
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_col, indicator.text.c_str(),
indicator.text.c_str() + indicator.text.length(), 0.0f, &text_clip_rect);
const ImVec2 text_pos = ImVec2(box.Max.x - padding - text_size.x, box.Min.y + padding);
RenderShadowedTextClipped(dl, UIStyle.MediumFont, text_pos, box.Max, text_col, indicator.text.c_str(),
indicator.text.c_str() + indicator.text.length(), &text_size, ImVec2(0.0f, 0.0f), 0.0f,
&box);
const ImVec2 icon_pos = ImVec2(box_min.x + padding, box_min.y + padding);
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, icon_pos, text_col, ICON_FA_STOPWATCH, nullptr,
0.0f, &text_clip_rect);
const ImVec2 icon_pos = ImVec2(box.Min.x + padding, box.Min.y + padding);
RenderShadowedTextClipped(dl, UIStyle.MediumFont, icon_pos, box.Max, text_col, ICON_FA_STOPWATCH, nullptr,
nullptr, ImVec2(0.0f, 0.0f), 0.0f, &box);
if (!indicator.active && opacity <= 0.01f)
{
@ -2470,7 +2540,7 @@ void Achievements::DrawGameOverlays()
++it;
}
position.x = box_min.x - padding;
position.x = box.Min.x - padding;
}
// Uncomment if there are any other overlays above this one.
@ -2480,83 +2550,193 @@ void Achievements::DrawGameOverlays()
#ifndef __ANDROID__
void Achievements::DrawPauseMenuOverlays()
void Achievements::DrawPauseMenuOverlays(float start_pos_y)
{
using ImGuiFullscreen::DarkerColor;
using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::ModAlpha;
using ImGuiFullscreen::UIStyle;
if (!HasActiveGame())
if (!HasActiveGame() || !HasAchievements())
return;
const auto lock = GetLock();
if (s_state.active_challenge_indicators.empty() && !s_state.active_progress_indicator.has_value())
return;
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
const float box_margin = LayoutScale(20.0f);
const float box_width = LayoutScale(450.0f);
const float box_padding = LayoutScale(15.0f);
const float box_content_width = box_width - box_padding - box_padding;
const float box_rounding = LayoutScale(20.0f);
const u32 box_background_color = ImGui::GetColorU32(ModAlpha(UIStyle.BackgroundColor, 0.8f));
const ImU32 title_text_color = ImGui::GetColorU32(UIStyle.BackgroundTextColor) | IM_COL32_A_MASK;
const ImU32 text_color = ImGui::GetColorU32(DarkerColor(UIStyle.BackgroundTextColor)) | IM_COL32_A_MASK;
const float paragraph_spacing = LayoutScale(10.0f);
const float text_spacing = LayoutScale(2.0f);
const ImGuiIO& io = ImGui::GetIO();
ImFont* font = UIStyle.MediumFont;
const float progress_height = LayoutScale(20.0f);
const float progress_rounding = LayoutScale(5.0f);
const float badge_size = LayoutScale(40.0f);
const float badge_text_width = box_content_width - badge_size - text_spacing - text_spacing;
const bool disconnected = rc_client_is_disconnected(s_state.client);
const int pending_count = disconnected ? rc_client_get_award_achievement_pending_count(s_state.client) : 0;
const ImVec2 image_size(LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY));
const float start_y =
LayoutScale(10.0f + 4.0f + 4.0f) + UIStyle.LargeFont->FontSize + (UIStyle.MediumFont->FontSize * 2.0f);
const float margin = LayoutScale(10.0f);
const float spacing = LayoutScale(10.0f);
const float padding = LayoutScale(10.0f);
ImDrawList* dl = ImGui::GetBackgroundDrawList();
const float max_text_width = ImGuiFullscreen::LayoutScale(300.0f);
const float row_width = max_text_width + padding + padding + image_size.x + spacing;
const float title_height = padding + font->FontSize + padding;
const auto get_achievement_height = [&badge_size, &badge_text_width,
&text_spacing](const rc_client_achievement_t* achievement, bool show_measured) {
const ImVec2 description_size = UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX,
badge_text_width, achievement->description);
const float text_height = UIStyle.MediumFont->FontSize + text_spacing + description_size.y;
return std::max(text_height, badge_size);
};
float box_height =
box_padding + box_padding + UIStyle.MediumFont->FontSize + paragraph_spacing + progress_height + paragraph_spacing;
if (pending_count > 0)
{
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing;
}
if (s_state.most_recent_unlock)
{
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing +
get_achievement_height(s_state.most_recent_unlock, false) +
(s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f);
}
if (s_state.achievement_nearest_completion)
{
box_height += UIStyle.MediumFont->FontSize + paragraph_spacing +
get_achievement_height(s_state.achievement_nearest_completion, true);
}
ImVec2 box_min = ImVec2(display_size.x - box_width - box_margin, start_pos_y + box_margin);
ImVec2 box_max = ImVec2(box_min.x + box_width, box_min.y + box_height);
ImVec2 text_pos = ImVec2(box_min.x + box_padding, box_min.y + box_padding);
ImVec2 text_size;
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
const auto draw_achievement_with_summary = [&box_max, &badge_text_width, &dl, &title_text_color, &text_color,
&text_spacing, &text_pos, &badge_size](
const rc_client_achievement_t* achievement, bool show_measured) {
const ImVec2 image_max = ImVec2(text_pos.x + badge_size, text_pos.y + badge_size);
ImVec2 badge_text_pos = ImVec2(image_max.x + text_spacing + text_spacing, text_pos.y);
const ImVec4 clip_rect = ImVec4(badge_text_pos.x, badge_text_pos.y, badge_text_pos.x + badge_text_width, box_max.y);
const ImVec2 description_size = UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX,
badge_text_width, achievement->description);
GPUTexture* badge_tex = ImGuiFullscreen::GetCachedTextureAsync(
GetCachedAchievementBadgePath(achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED));
dl->AddImage(badge_tex, text_pos, image_max);
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, title_text_color, achievement->title,
nullptr, 0.0f, &clip_rect);
badge_text_pos.y += UIStyle.MediumFont->FontSize + text_spacing;
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, badge_text_pos, text_color, achievement->description,
nullptr, badge_text_width, &clip_rect);
badge_text_pos.y += description_size.y;
text_pos.y = badge_text_pos.y;
};
TinyString buffer;
// title
{
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color,
TRANSLATE_DISAMBIG("Achievements", "Achievements Unlocked", "Pause Menu"));
const float unlocked_fraction = static_cast<float>(s_state.game_summary.num_unlocked_achievements) /
static_cast<float>(s_state.game_summary.num_core_achievements);
buffer.format("{}%", static_cast<u32>(std::ceil(unlocked_fraction * 100.0f)));
text_size =
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, buffer.c_str(), buffer.end_ptr());
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize,
ImVec2(text_pos.x + (box_content_width - text_size.x), text_pos.y), text_color, buffer.c_str(),
buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
const ImRect progress_bb(text_pos, text_pos + ImVec2(box_content_width, progress_height));
const u32 progress_color = ImGui::GetColorU32(DarkerColor(UIStyle.SecondaryColor));
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(UIStyle.PrimaryDarkColor),
progress_rounding);
dl->AddRectFilled(progress_bb.Min,
ImVec2(progress_bb.Min.x + unlocked_fraction * progress_bb.GetWidth(), progress_bb.Max.y),
progress_color, progress_rounding);
buffer.format("{}/{}", s_state.game_summary.num_unlocked_achievements, s_state.game_summary.num_core_achievements);
text_size =
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, buffer.c_str(), buffer.end_ptr());
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize,
ImVec2(progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f)),
text_color, buffer.c_str(), buffer.end_ptr());
text_pos.y += progress_height + paragraph_spacing;
if (pending_count > 0)
{
buffer.format(ICON_EMOJI_WARNING " {}",
TRANSLATE_PLURAL_SSTR("Achievements", "%n unlocks have not been confirmed by the server.",
"Pause Menu", pending_count));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, title_text_color, buffer.c_str(),
buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
}
}
if (s_state.most_recent_unlock)
{
buffer.format(ICON_FA_LOCK_OPEN " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Most Recent", "Pause Menu"));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(),
buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
draw_achievement_with_summary(s_state.most_recent_unlock, false);
// extra spacing if we have two
text_pos.y += s_state.achievement_nearest_completion ? (paragraph_spacing + paragraph_spacing) : 0.0f;
}
if (s_state.achievement_nearest_completion)
{
buffer.format(ICON_FA_LOCK " {}", TRANSLATE_DISAMBIG_SV("Achievements", "Nearest Completion", "Pause Menu"));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(),
buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize + paragraph_spacing;
draw_achievement_with_summary(s_state.achievement_nearest_completion, true);
text_pos.y += paragraph_spacing;
}
// Challenge indicators
if (!s_state.active_challenge_indicators.empty())
{
const ImVec2 box_min(io.DisplaySize.x - row_width - margin, start_y + margin);
const ImVec2 box_max(box_min.x + row_width,
box_min.y + title_height +
(static_cast<float>(s_state.active_challenge_indicators.size()) * (image_size.y + padding)));
box_height = box_padding + box_padding + UIStyle.MediumFont->FontSize;
for (size_t i = 0; i < s_state.active_challenge_indicators.size(); i++)
{
const AchievementChallengeIndicator& indicator = s_state.active_challenge_indicators[i];
box_height += paragraph_spacing + get_achievement_height(indicator.achievement, false) +
((i == s_state.active_challenge_indicators.size() - 1) ? paragraph_spacing : 0.0f);
}
ImDrawList* dl = ImGui::GetBackgroundDrawList();
dl->AddRectFilled(box_min, box_max, IM_COL32(0x21, 0x21, 0x21, 200), LayoutScale(10.0f));
dl->AddText(font, font->FontSize, ImVec2(box_min.x + padding, box_min.y + padding), IM_COL32(255, 255, 255, 255),
TRANSLATE("Achievements", "Active Challenge Achievements"));
box_min = ImVec2(box_min.x, box_max.y + box_margin);
box_max = ImVec2(box_min.x + box_width, box_min.y + box_height);
text_pos = ImVec2(box_min.x + box_padding, box_min.y + box_padding);
const float y_advance = image_size.y + spacing;
const float acheivement_name_offset = (image_size.y - font->FontSize) / 2.0f;
const float max_non_ellipised_text_width = max_text_width - LayoutScale(10.0f);
ImVec2 position(box_min.x + padding, box_min.y + title_height);
dl->AddRectFilled(box_min, box_max, box_background_color, box_rounding);
buffer.format(ICON_FA_STOPWATCH " {}",
TRANSLATE_DISAMBIG_SV("Achievements", "Active Challenge Achievements", "Pause Menu"));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_color, buffer.c_str(),
buffer.end_ptr());
text_pos.y += UIStyle.MediumFont->FontSize;
for (const AchievementChallengeIndicator& indicator : s_state.active_challenge_indicators)
{
GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(indicator.badge_path);
if (!badge)
continue;
dl->AddImage(badge, position, position + image_size);
const char* achievement_title = indicator.achievement->title;
const char* achievement_title_end = achievement_title + std::strlen(indicator.achievement->title);
const char* remaining_text = nullptr;
const ImVec2 text_width(font->CalcTextSizeA(font->FontSize, max_non_ellipised_text_width, 0.0f, achievement_title,
achievement_title_end, &remaining_text));
const ImVec2 text_position(position.x + image_size.x + spacing, position.y + acheivement_name_offset);
const ImVec4 text_bbox(text_position.x, text_position.y, text_position.x + max_text_width,
text_position.y + image_size.y);
const u32 text_color = IM_COL32(255, 255, 255, 255);
if (remaining_text < achievement_title_end)
{
dl->AddText(font, font->FontSize, text_position, text_color, achievement_title, remaining_text, 0.0f,
&text_bbox);
dl->AddText(font, font->FontSize, ImVec2(text_position.x + text_width.x, text_position.y), text_color, "...",
nullptr, 0.0f, &text_bbox);
}
else
{
dl->AddText(font, font->FontSize, text_position, text_color, achievement_title, achievement_title_end, 0.0f,
&text_bbox);
}
position.y += y_advance;
text_pos.y += paragraph_spacing;
draw_achievement_with_summary(indicator.achievement, false);
text_pos.y += paragraph_spacing;
}
}
}
@ -2603,7 +2783,6 @@ void Achievements::DrawAchievementsWindow()
const ImVec4 background = ImGuiFullscreen::ModAlpha(UIStyle.BackgroundColor, alpha);
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(UIStyle.BackgroundColor, heading_alpha);
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
const u32 text_color = ImGui::GetColorU32(ImGuiCol_Text);
const float heading_height = LayoutScale(heading_height_unscaled);
bool close_window = false;
@ -2653,8 +2832,8 @@ void Achievements::DrawAchievementsWindow()
top += UIStyle.LargeFont->FontSize + spacing;
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, text_color, text.c_str(), text.end_ptr(),
nullptr, ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, ImGui::GetColorU32(ImGuiCol_Text),
text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
if (s_state.game_summary.num_core_achievements > 0)
@ -2679,22 +2858,27 @@ void Achievements::DrawAchievementsWindow()
top += UIStyle.MediumFont->FontSize + spacing;
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, text_color, text.c_str(),
text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
RenderShadowedTextClipped(
UIStyle.MediumFont, summary_bb.Min, summary_bb.Max,
ImGui::GetColorU32(ImGuiFullscreen::DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text])), text.c_str(),
text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
if (s_state.game_summary.num_core_achievements > 0)
{
const float progress_height = LayoutScale(20.0f);
const float progress_rounding = LayoutScale(5.0f);
const ImRect progress_bb(ImVec2(left, top), ImVec2(right, top + progress_height));
const float fraction = static_cast<float>(s_state.game_summary.num_unlocked_achievements) /
static_cast<float>(s_state.game_summary.num_core_achievements);
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(UIStyle.PrimaryDarkColor));
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(UIStyle.PrimaryDarkColor),
progress_rounding);
dl->AddRectFilled(progress_bb.Min,
ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
ImGui::GetColorU32(UIStyle.SecondaryColor));
ImGui::GetColorU32(UIStyle.SecondaryColor), progress_rounding);
text.format("{}%", static_cast<int>(std::round(fraction * 100.0f)));
text_size = ImGui::CalcTextSize(text.c_str(), text.end_ptr());
text_size =
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr());
const ImVec2 text_pos(
progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f));
@ -2718,19 +2902,23 @@ void Achievements::DrawAchievementsWindow()
"achievements", background, 0.0f, ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f), 0))
{
static bool buckets_collapsed[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = {};
static const char* bucket_names[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = {
TRANSLATE_NOOP("Achievements", "Unknown"), TRANSLATE_NOOP("Achievements", "Locked"),
TRANSLATE_NOOP("Achievements", "Unlocked"), TRANSLATE_NOOP("Achievements", "Unsupported"),
TRANSLATE_NOOP("Achievements", "Unofficial"), TRANSLATE_NOOP("Achievements", "Recently Unlocked"),
TRANSLATE_NOOP("Achievements", "Active Challenges"), TRANSLATE_NOOP("Achievements", "Almost There"),
static constexpr std::pair<const char*, const char*> bucket_names[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = {
{ICON_FA_EXCLAMATION_TRIANGLE, TRANSLATE_NOOP("Achievements", "Unknown")},
{ICON_FA_LOCK, TRANSLATE_NOOP("Achievements", "Locked")},
{ICON_FA_UNLOCK, TRANSLATE_NOOP("Achievements", "Unlocked")},
{ICON_FA_EXCLAMATION_TRIANGLE, TRANSLATE_NOOP("Achievements", "Unsupported")},
{ICON_FA_QUESTION_CIRCLE, TRANSLATE_NOOP("Achievements", "Unofficial")},
{ICON_EMOJI_UNLOCKED, TRANSLATE_NOOP("Achievements", "Recently Unlocked")},
{ICON_FA_STOPWATCH, TRANSLATE_NOOP("Achievements", "Active Challenges")},
{ICON_FA_RULER_HORIZONTAL, TRANSLATE_NOOP("Achievements", "Almost There")},
};
ImGuiFullscreen::ResetFocusHere();
ImGuiFullscreen::BeginMenuButtons();
for (u32 bucket_type : {RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED, RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED, RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED, RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL, RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED})
{
for (u32 bucket_idx = 0; bucket_idx < s_state.achievement_list->num_buckets; bucket_idx++)
@ -2743,9 +2931,10 @@ void Achievements::DrawAchievementsWindow()
// TODO: Once subsets are supported, this will need to change.
bool& bucket_collapsed = buckets_collapsed[bucket.bucket_type];
bucket_collapsed ^=
ImGuiFullscreen::MenuHeadingButton(Host::TranslateToCString("Achievements", bucket_names[bucket.bucket_type]),
bucket_collapsed ? ICON_FA_CHEVRON_DOWN : ICON_FA_CHEVRON_UP);
bucket_collapsed ^= ImGuiFullscreen::MenuHeadingButton(
TinyString::from_format("{} {}", bucket_names[bucket.bucket_type].first,
Host::TranslateToStringView("Achievements", bucket_names[bucket.bucket_type].second)),
bucket_collapsed ? ICON_FA_CHEVRON_DOWN : ICON_FA_CHEVRON_UP);
if (!bucket_collapsed)
{
for (u32 i = 0; i < bucket.num_achievements; i++)
@ -2758,6 +2947,13 @@ void Achievements::DrawAchievementsWindow()
}
ImGuiFullscreen::EndFullscreenWindow();
ImGuiFullscreen::SetFullscreenStatusText(std::array{
std::make_pair(ICON_PF_ACHIEVEMENTS_MISSABLE, TRANSLATE_SV("Achievements", "Missable")),
std::make_pair(ICON_PF_ACHIEVEMENTS_PROGRESSION, TRANSLATE_SV("Achievements", "Progression")),
std::make_pair(ICON_PF_ACHIEVEMENTS_WIN, TRANSLATE_SV("Achievements", "Win Condition")),
std::make_pair(ICON_FA_LOCK, TRANSLATE_SV("Achievements", "Locked")),
std::make_pair(ICON_EMOJI_UNLOCKED, TRANSLATE_SV("Achievements", "Unlocked")),
});
ImGuiFullscreen::SetFullscreenFooterText(
std::array{std::make_pair(ImGuiFullscreen::IsGamepadInputSource() ? ICON_PF_XBOX_DPAD_UP_DOWN :
ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN,
@ -2774,6 +2970,8 @@ void Achievements::DrawAchievementsWindow()
void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
{
using ImGuiFullscreen::DarkerColor;
using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT;
using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::LayoutUnscale;
using ImGuiFullscreen::RenderShadowedTextClipped;
@ -2782,17 +2980,20 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
static constexpr float alpha = 0.8f;
static constexpr float progress_height_unscaled = 20.0f;
static constexpr float progress_spacing_unscaled = 5.0f;
static constexpr float progress_rounding_unscaled = 5.0f;
static constexpr float spacing_unscaled = 4.0f;
const float spacing = ImGuiFullscreen::LayoutScale(4.0f);
const u32 text_color = ImGui::GetColorU32(ImGuiCol_Text);
const float spacing = ImGuiFullscreen::LayoutScale(spacing_unscaled);
const u32 text_color = ImGui::GetColorU32(UIStyle.SecondaryTextColor);
const u32 summary_color = ImGui::GetColorU32(DarkerColor(UIStyle.SecondaryTextColor));
const u32 rarity_color = ImGui::GetColorU32(DarkerColor(DarkerColor(UIStyle.SecondaryTextColor)));
const bool is_unlocked = (cheevo->state == RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED);
const std::string_view measured_progress(cheevo->measured_progress);
const bool is_measured = !is_unlocked && !measured_progress.empty();
const float unlock_size = is_unlocked ? (spacing + ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE) : 0.0f;
const ImVec2 points_template_size(UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f,
TRANSLATE("Achievements", "XXX points")));
const float unlock_rarity_height = spacing_unscaled + ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE;
const ImVec2 points_template_size = UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f,
TRANSLATE("Achievements", "XXX points"));
const size_t summary_length = std::strlen(cheevo->description);
const float summary_wrap_width =
(ImGui::GetCurrentWindow()->WorkRect.GetWidth() - (ImGui::GetStyle().FramePadding.x * 2.0f) -
@ -2802,37 +3003,26 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
cheevo->description + summary_length));
// Messy, but need to undo LayoutScale in MenuButtonFrame()...
const float extra_summary_height = LayoutUnscale(std::max(summary_text_size.y - UIStyle.MediumFont->FontSize, 0.0f));
const float extra_summary_height = std::max(LayoutUnscale(summary_text_size.y) - LAYOUT_MENU_BUTTON_HEIGHT, 0.0f);
ImRect bb;
bool visible, hovered;
const bool clicked = ImGuiFullscreen::MenuButtonFrame(
TinyString::from_format("chv_{}", cheevo->id), true,
!is_measured ? ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT + extra_summary_height + unlock_size :
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT + extra_summary_height + progress_height_unscaled +
progress_spacing_unscaled,
!is_measured ? (LAYOUT_MENU_BUTTON_HEIGHT + extra_summary_height + unlock_rarity_height) :
(LAYOUT_MENU_BUTTON_HEIGHT + extra_summary_height + unlock_rarity_height + progress_height_unscaled +
progress_spacing_unscaled),
&visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
if (!visible)
return;
std::string* badge_path;
if (const auto badge_it = std::find_if(s_state.achievement_badge_paths.begin(), s_state.achievement_badge_paths.end(),
[cheevo](const auto& it) { return (it.first == cheevo); });
badge_it != s_state.achievement_badge_paths.end())
{
badge_path = &badge_it->second;
}
else
{
std::string new_badge_path = Achievements::GetAchievementBadgePath(cheevo, cheevo->state);
badge_path = &s_state.achievement_badge_paths.emplace_back(cheevo, std::move(new_badge_path)).second;
}
const std::string& badge_path = GetCachedAchievementBadgePath(cheevo, cheevo->state);
const ImVec2 image_size(
LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT));
if (!badge_path->empty())
if (!badge_path.empty())
{
GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(*badge_path);
GPUTexture* badge = ImGuiFullscreen::GetCachedTextureAsync(badge_path);
if (badge)
{
ImGui::GetWindowDrawList()->AddImage(badge, bb.Min, bb.Min + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f),
@ -2877,6 +3067,9 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f);
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(points_start, midpoint));
const ImRect summary_bb(ImVec2(text_start_x, midpoint), ImVec2(points_start, midpoint + summary_text_size.y));
const ImRect unlock_rarity_bb(summary_bb.Min.x, summary_bb.Max.y + spacing, summary_bb.Max.x,
summary_bb.Max.y +
LayoutScale(spacing_unscaled + ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE));
const ImRect points_bb(ImVec2(points_start, midpoint), bb.Max);
const ImRect lock_bb(ImVec2(points_template_start + ((points_template_size.x - right_icon_size.x) * 0.5f), bb.Min.y),
ImVec2(bb.Max.x, midpoint));
@ -2885,40 +3078,54 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
RenderShadowedTextClipped(UIStyle.LargeFont, lock_bb.Min, lock_bb.Max, text_color, right_icon_text, nullptr,
&right_icon_size, ImVec2(0.0f, 0.0f), 0.0f, &lock_bb);
RenderShadowedTextClipped(UIStyle.MediumFont, points_bb.Min, points_bb.Max, summary_color, text.c_str(),
text.end_ptr(), &points_size, ImVec2(0.0f, 0.0f), 0.0f, &points_bb);
if (cheevo->description && summary_length > 0)
{
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, text_color, cheevo->description,
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, summary_color, cheevo->description,
cheevo->description + summary_length, &summary_text_size, ImVec2(0.0f, 0.0f),
summary_wrap_width, &summary_bb);
}
RenderShadowedTextClipped(UIStyle.MediumFont, points_bb.Min, points_bb.Max, text_color, text.c_str(), text.end_ptr(),
&points_size, ImVec2(0.0f, 0.0f), 0.0f, &points_bb);
// display hc if hc is active
const float rarity_to_display = IsHardcoreModeActive() ? cheevo->rarity_hardcore : cheevo->rarity;
if (is_unlocked)
{
TinyString date;
FullscreenUI::TimeToPrintableString(&date, cheevo->unlock_time);
text.format(TRANSLATE_FS("Achievements", "Unlocked: {}"), date);
text.format(TRANSLATE_FS("Achievements", "Unlocked: {} | {:.1f}% of players have this achievement"), date,
rarity_to_display);
const ImRect unlock_bb(summary_bb.Min.x, summary_bb.Max.y + spacing, summary_bb.Max.x, bb.Max.y);
RenderShadowedTextClipped(UIStyle.MediumFont, unlock_bb.Min, unlock_bb.Max, text_color, text.c_str(),
text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &unlock_bb);
RenderShadowedTextClipped(UIStyle.MediumFont, unlock_rarity_bb.Min, unlock_rarity_bb.Max, rarity_color,
text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &unlock_rarity_bb);
}
else if (is_measured)
else
{
text.format(TRANSLATE_FS("Achievements", "{:.1f}% of players have this achievement"), rarity_to_display);
RenderShadowedTextClipped(UIStyle.MediumFont, unlock_rarity_bb.Min, unlock_rarity_bb.Max, rarity_color,
text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &unlock_rarity_bb);
}
if (!is_unlocked && is_measured)
{
ImDrawList* dl = ImGui::GetWindowDrawList();
const float progress_height = LayoutScale(progress_height_unscaled);
const float progress_spacing = LayoutScale(progress_spacing_unscaled);
const float top = midpoint + UIStyle.MediumFont->FontSize + progress_spacing;
const ImRect progress_bb(ImVec2(text_start_x, top), ImVec2(bb.Max.x, top + progress_height));
const float progress_rounding = LayoutScale(progress_rounding_unscaled);
const ImRect progress_bb(summary_bb.Min.x, unlock_rarity_bb.Max.y + progress_spacing,
summary_bb.Max.x - progress_spacing,
unlock_rarity_bb.Max.y + progress_spacing + progress_height);
const float fraction = cheevo->measured_percent * 0.01f;
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIStyle.PrimaryDarkColor));
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIStyle.PrimaryDarkColor),
progress_rounding);
dl->AddRectFilled(progress_bb.Min, ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
ImGui::GetColorU32(ImGuiFullscreen::UIStyle.SecondaryColor));
ImGui::GetColorU32(ImGuiFullscreen::UIStyle.SecondaryColor), progress_rounding);
const ImVec2 text_size =
ImGui::CalcTextSize(measured_progress.data(), measured_progress.data() + measured_progress.size());
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, measured_progress.data(),
measured_progress.data() + measured_progress.size());
const ImVec2 text_pos(progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f));
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos,
@ -2955,6 +3162,7 @@ bool Achievements::PrepareLeaderboardsWindow()
void Achievements::DrawLeaderboardsWindow()
{
using ImGuiFullscreen::DarkerColor;
using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::RenderShadowedTextClipped;
using ImGuiFullscreen::UIStyle;
@ -2975,6 +3183,7 @@ void Achievements::DrawLeaderboardsWindow()
bool close_leaderboard_on_exit = false;
ImRect bb;
SmallString text;
const ImVec4 background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIStyle.BackgroundColor, alpha);
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIStyle.BackgroundColor, heading_alpha);
@ -3034,7 +3243,6 @@ void Achievements::DrawLeaderboardsWindow()
float left = bb.Min.x + padding + image_height + spacing;
float right = bb.Max.x - padding;
float top = bb.Min.y + padding;
SmallString text;
if (!is_leaderboard_open)
{
@ -3063,6 +3271,7 @@ void Achievements::DrawLeaderboardsWindow()
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, text_color, text.c_str(), text.end_ptr(),
nullptr, ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
u32 summary_color;
if (is_leaderboard_open)
{
const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.LargeFont->FontSize));
@ -3070,10 +3279,12 @@ void Achievements::DrawLeaderboardsWindow()
top += UIStyle.LargeFont->FontSize + spacing_small;
RenderShadowedTextClipped(UIStyle.LargeFont, subtitle_bb.Min, subtitle_bb.Max, text_color, text.c_str(),
text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &subtitle_bb);
RenderShadowedTextClipped(UIStyle.LargeFont, subtitle_bb.Min, subtitle_bb.Max,
ImGui::GetColorU32(DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text])),
text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &subtitle_bb);
text.assign(s_state.open_leaderboard->description);
summary_color = ImGui::GetColorU32(DarkerColor(DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text])));
}
else
{
@ -3081,12 +3292,13 @@ void Achievements::DrawLeaderboardsWindow()
for (u32 i = 0; i < s_state.leaderboard_list->num_buckets; i++)
count += s_state.leaderboard_list->buckets[i].num_leaderboards;
text = TRANSLATE_PLURAL_SSTR("Achievements", "This game has %n leaderboards.", "Leaderboard count", count);
summary_color = ImGui::GetColorU32(DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]));
}
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
top += UIStyle.MediumFont->FontSize + spacing_small;
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, text_color, text.c_str(),
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, summary_color, text.c_str(),
text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
if (!is_leaderboard_open && !Achievements::IsHardcoreModeActive())
@ -3094,11 +3306,15 @@ void Achievements::DrawLeaderboardsWindow()
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
top += UIStyle.MediumFont->FontSize + spacing_small;
RenderShadowedTextClipped(
UIStyle.MediumFont, hardcore_warning_bb.Min, hardcore_warning_bb.Max, text_color,
TRANSLATE("Achievements",
"Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only."),
nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &hardcore_warning_bb);
text.format(
ICON_EMOJI_WARNING " {}",
TRANSLATE_SV("Achievements",
"Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only."));
RenderShadowedTextClipped(UIStyle.MediumFont, hardcore_warning_bb.Min, hardcore_warning_bb.Max,
ImGui::GetColorU32(DarkerColor(DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]))),
text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), 0.0f,
&hardcore_warning_bb);
}
if (is_leaderboard_open)
@ -3144,17 +3360,19 @@ void Achievements::DrawLeaderboardsWindow()
bb.Min.x += LayoutScale(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING);
bb.Max.x -= LayoutScale(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING);
const u32 heading_color = ImGui::GetColorU32(DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]));
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
float text_start_x = bb.Min.x + LayoutScale(15.0f) + padding;
const ImRect rank_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(UIStyle.LargeFont, rank_bb.Min, rank_bb.Max, text_color,
RenderShadowedTextClipped(UIStyle.LargeFont, rank_bb.Min, rank_bb.Max, heading_color,
TRANSLATE("Achievements", "Rank"), nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f,
&rank_bb);
text_start_x += rank_column_width + column_spacing;
const ImRect user_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(UIStyle.LargeFont, user_bb.Min, user_bb.Max, text_color,
RenderShadowedTextClipped(UIStyle.LargeFont, user_bb.Min, user_bb.Max, heading_color,
TRANSLATE("Achievements", "Name"), nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f,
&user_bb);
text_start_x += name_column_width + column_spacing;
@ -3167,7 +3385,7 @@ void Achievements::DrawLeaderboardsWindow()
const ImRect score_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(
UIStyle.LargeFont, score_bb.Min, score_bb.Max, text_color,
UIStyle.LargeFont, score_bb.Min, score_bb.Max, heading_color,
Host::TranslateToCString(
"Achievements",
value_headings[std::min<u8>(s_state.open_leaderboard->format, NUM_RC_CLIENT_LEADERBOARD_FORMATS - 1)]),
@ -3175,7 +3393,7 @@ void Achievements::DrawLeaderboardsWindow()
text_start_x += time_column_width + column_spacing;
const ImRect date_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(UIStyle.LargeFont, date_bb.Min, date_bb.Max, text_color,
RenderShadowedTextClipped(UIStyle.LargeFont, date_bb.Min, date_bb.Max, heading_color,
TRANSLATE("Achievements", "Date Submitted"), nullptr, nullptr, ImVec2(0.0f, 0.0f),
0.0f, &date_bb);
@ -3247,7 +3465,7 @@ void Achievements::DrawLeaderboardsWindow()
{
for (u32 i = 0; i < s_state.leaderboard_nearby_entries->num_entries; i++)
{
DrawLeaderboardEntry(s_state.leaderboard_nearby_entries->entries[i],
DrawLeaderboardEntry(s_state.leaderboard_nearby_entries->entries[i], i,
static_cast<s32>(i) == s_state.leaderboard_nearby_entries->user_index,
rank_column_width, name_column_width, time_column_width, column_spacing);
}
@ -3267,24 +3485,23 @@ void Achievements::DrawLeaderboardsWindow()
{
for (u32 i = 0; i < list->num_entries; i++)
{
DrawLeaderboardEntry(list->entries[i], static_cast<s32>(i) == list->user_index, rank_column_width,
DrawLeaderboardEntry(list->entries[i], i, static_cast<s32>(i) == list->user_index, rank_column_width,
name_column_width, time_column_width, column_spacing);
}
}
// Fetch next chunk if the loading indicator becomes visible (i.e. we scrolled enough).
bool visible, hovered;
ImGuiFullscreen::MenuButtonFrame(TRANSLATE("Achievements", "Loading..."), false,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, &visible, &hovered,
&bb.Min, &bb.Max);
text.format(ICON_FA_HOURGLASS_HALF " {}", TRANSLATE_SV("Achievements", "Loading..."));
ImGuiFullscreen::MenuButtonFrame(text.c_str(), false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
&visible, &hovered, &bb.Min, &bb.Max);
if (visible)
{
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, text_color,
TRANSLATE("Achievements", "Loading..."), nullptr, nullptr, ImVec2(0, 0), 0.0f,
&title_bb);
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, text_color, text.c_str(),
text.end_ptr(), nullptr, ImVec2(0, 0), 0.0f, &title_bb);
if (!s_state.leaderboard_fetch_handle)
FetchNextLeaderboardEntries();
@ -3310,10 +3527,10 @@ void Achievements::DrawLeaderboardsWindow()
}
if (close_leaderboard_on_exit)
CloseLeaderboard();
FullscreenUI::BeginTransition(&CloseLeaderboard);
}
void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, bool is_self,
void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, u32 index, bool is_self,
float rank_column_width, float name_column_width, float time_column_width,
float column_spacing)
{
@ -3337,7 +3554,11 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
text.format("{}", entry.rank);
const u32 text_color = is_self ? IM_COL32(255, 242, 0, 255) : ImGui::GetColorU32(ImGuiCol_Text);
const u32 text_color =
is_self ?
IM_COL32(255, 242, 0, 255) :
ImGui::GetColorU32(((index % 2) == 0) ? ImGuiFullscreen::DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text]) :
ImGui::GetStyle().Colors[ImGuiCol_Text]);
const ImRect rank_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
RenderShadowedTextClipped(UIStyle.LargeFont, rank_bb.Min, rank_bb.Max, text_color, text.c_str(), text.end_ptr(),
@ -3412,21 +3633,21 @@ void Achievements::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboar
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
const float text_start_x = bb.Min.x + LayoutScale(15.0f);
const u32 text_color = ImGui::GetColorU32(ImGuiCol_Text);
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max);
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, text_color, lboard->title, nullptr, nullptr,
ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
RenderShadowedTextClipped(UIStyle.LargeFont, title_bb.Min, title_bb.Max, ImGui::GetColorU32(ImGuiCol_Text),
lboard->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &title_bb);
if (lboard->description && lboard->description[0] != '\0')
{
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max, text_color, lboard->description,
nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
RenderShadowedTextClipped(UIStyle.MediumFont, summary_bb.Min, summary_bb.Max,
ImGui::GetColorU32(ImGuiFullscreen::DarkerColor(ImGui::GetStyle().Colors[ImGuiCol_Text])),
lboard->description, nullptr, nullptr, ImVec2(0.0f, 0.0f), 0.0f, &summary_bb);
}
if (pressed)
OpenLeaderboard(lboard);
FullscreenUI::BeginTransition([id = lboard->id]() { OpenLeaderboardById(id); });
}
#endif // __ANDROID__

View file

@ -155,10 +155,16 @@ const std::string& GetRichPresenceString();
/// Returns the URL for the current icon of the game
const std::string& GetGameIconURL();
/// Returns the path for the current icon of the game
const std::string& GetGameIconPath();
/// Returns the RetroAchievements title for the current game.
/// Should be called with the lock held.
const std::string& GetGameTitle();
/// Returns the path for the game that is current hashed/running.
const std::string& GetGamePath();
/// Returns the logged-in user name.
const char* GetLoggedInUserName();
@ -176,7 +182,11 @@ void ClearUIState();
void DrawGameOverlays();
/// Draws ImGui overlays when paused.
void DrawPauseMenuOverlays();
void DrawPauseMenuOverlays(float start_pos_y);
/// Updates the stored most-recent and closest-to-completion achievements.
/// Call before calling DrawPauseMenuOverlays() for the first time.
void UpdateRecentUnlockAndAlmostThere();
#ifndef __ANDROID__

View file

@ -13,7 +13,6 @@ namespace Achievements {
rc_client_t* GetClient();
const rc_client_user_game_summary_t& GetGameSummary();
const std::string& GetGameIconPath();
std::string GetAchievementBadgePath(const rc_client_achievement_t* achievement, int state,
bool download_if_missing = true);

View file

@ -28,7 +28,7 @@ static constexpr ImageInfo::Hash MakeHashFromString(const char str[])
// Launch console BIOS is de-prioritized due to bugs.
// Late PAL is de-prioritized due to additional regional checks that break import booting without fast boot.
// PS2 is de-prioritized due to requiring a dynamic fast boot patch.
// PS2 PAL is further de-prioritized due to additonal region checks.
// PS2 PAL is further de-prioritized due to additional region checks.
static constexpr const ImageInfo s_image_info_by_hash[] = {
{"SCPH-1000, DTL-H1000 (v1.0)", ConsoleRegion::NTSC_J, true, ImageInfo::FastBootPatch::Type1, 50, MakeHashFromString("239665b1a3dade1b5a52c06338011044")},
{"SCPH-1001, 5003, DTL-H1201, H3001 (v2.2 12-04-95 A)", ConsoleRegion::NTSC_U, false, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("924e392ed05558ffdb115408c263dccf")},
@ -47,7 +47,7 @@ static constexpr const ImageInfo s_image_info_by_hash[] = {
{"SCPH-7000, 7500, 9000 (v4.0 08-18-97 J)", ConsoleRegion::NTSC_J, true, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("8e4c14f567745eff2f0408c8129f72a6")},
{"SCPH-7000W (v4.1 11-14-97 A)", ConsoleRegion::NTSC_J, true, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("b84be139db3ee6cbd075630aa20a6553")},
{"SCPH-7001, 7501, 7503, 9001, 9003, 9903 (v4.1 12-16-97 A)", ConsoleRegion::NTSC_U, false, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("1e68c231d0896b7eadcad1d7d8e76129")},
{"SCPH-7002, 7502, 9002 (v4.1 12-16-97 E)", ConsoleRegion::PAL, false, ImageInfo::FastBootPatch::Type1, 20, MakeHashFromString("b9d9a0286c33dc6b7237bb13cd46fdee")},
{"SCPH-7002, 7502, 9002 (v4.1 12-16-97 E)", ConsoleRegion::PAL, false, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("b9d9a0286c33dc6b7237bb13cd46fdee")},
{"SCPH-100 (v4.3 03-11-00 J)", ConsoleRegion::NTSC_J, true, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("8abc1b549a4a80954addc48ef02c4521")},
{"SCPH-101 (v4.4 03-24-00 A)", ConsoleRegion::NTSC_U, false, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("9a09ab7e49b422c007e6d54d7c49b965")},
{"SCPH-101 (v4.5 05-25-00 A)", ConsoleRegion::NTSC_U, false, ImageInfo::FastBootPatch::Type1, 10, MakeHashFromString("6e3735ff4c7dc899ee98981385f6f3d0")},

View file

@ -444,6 +444,7 @@ struct CDROMState
bool last_sector_header_valid = false; // TODO: Rename to "logical pause" or something.
bool last_subq_needs_update = false;
bool cdda_auto_pause_pending = false;
u8 cdda_report_start_delay = 0;
u8 last_cdda_report_frame_nibble = 0xFF;
u8 play_track_number_bcd = 0xFF;
@ -638,6 +639,7 @@ TickCount CDROM::SoftReset(TickCount ticks_late)
s_state.play_after_seek = false;
s_state.muted = false;
s_state.adpcm_muted = false;
s_state.cdda_auto_pause_pending = false;
s_state.cdda_report_start_delay = 0;
s_state.last_cdda_report_frame_nibble = 0xFF;
@ -746,6 +748,7 @@ bool CDROM::DoState(StateWrapper& sw)
sw.DoBytes(&s_state.last_sector_subheader, sizeof(s_state.last_sector_subheader));
sw.Do(&s_state.last_sector_header_valid);
sw.DoBytes(&s_state.last_subq, sizeof(s_state.last_subq));
sw.DoEx(&s_state.cdda_auto_pause_pending, 81, false);
sw.DoEx(&s_state.cdda_report_start_delay, 72, static_cast<u8>(0));
sw.Do(&s_state.last_cdda_report_frame_nibble);
sw.Do(&s_state.play_track_number_bcd);
@ -1578,7 +1581,7 @@ u32 CDROM::GetSectorsPerTrack(CDImage::LBA lba)
TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change)
{
if (g_settings.cdrom_seek_speedup == 0)
return System::ScaleTicksToOverclock(g_settings.cdrom_max_speedup_cycles);
return System::ScaleTicksToOverclock(g_settings.cdrom_max_seek_speedup_cycles);
u32 ticks = 0;
@ -1683,7 +1686,10 @@ TickCount CDROM::GetTicksForPause()
return 27000;
if (g_settings.cdrom_read_speedup == 0 && CanUseReadSpeedup())
return System::ScaleTicksToOverclock(g_settings.cdrom_max_speedup_cycles);
{
return System::ScaleTicksToOverclock(
std::max(g_settings.cdrom_max_read_speedup_cycles, g_settings.cdrom_max_seek_speedup_cycles));
}
const u32 sectors_per_track = GetSectorsPerTrack(s_state.current_lba);
const TickCount ticks_per_read = GetTicksForRead();
@ -3307,6 +3313,15 @@ void CDROM::DoSectorRead()
}
else if (s_state.mode.auto_pause)
{
if (s_state.cdda_auto_pause_pending)
{
DEV_COLOR_LOG(StrongRed, "Auto pause at the start of track {:02x} ({} LBA {})", subq.track_number_bcd,
LBAToMSFString(s_state.current_lba), s_state.current_lba);
s_state.cdda_auto_pause_pending = false;
StopReadingWithDataEnd();
return;
}
// Only update the tracked track-to-pause-after once auto pause is enabled. Pitball's menu music starts mid-second,
// and there's no pregap, so the first couple of reports are for the previous track. It doesn't enable autopause
// until receiving a couple, and it's actually playing the track it wants.
@ -3314,14 +3329,13 @@ void CDROM::DoSectorRead()
{
// track number was not specified, but we've found the track now
s_state.play_track_number_bcd = subq.track_number_bcd;
DEBUG_LOG("Setting playing track number to {}", s_state.play_track_number_bcd);
DEV_LOG("Setting playing track number to {}", s_state.play_track_number_bcd);
}
else if (subq.track_number_bcd != s_state.play_track_number_bcd)
else if (s_state.play_track_number_bcd != subq.track_number_bcd)
{
// we don't want to update the position if the track changes, so we check it before reading the actual sector.
DEV_LOG("Auto pause at the start of track {:02x} (LBA {})", subq.track_number_bcd, s_state.current_lba);
StopReadingWithDataEnd();
return;
DEV_LOG("Pending auto pause at the start of track {:02x} ({} LBA {})", subq.track_number_bcd,
LBAToMSFString(s_state.current_lba), s_state.current_lba);
s_state.cdda_auto_pause_pending = true;
}
}
@ -3667,6 +3681,8 @@ void CDROM::ResetCurrentXAFile()
void CDROM::ResetAudioDecoder()
{
s_state.cdda_auto_pause_pending = false;
ResetCurrentXAFile();
s_state.xa_last_samples.fill(0);
@ -3859,7 +3875,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessCDDASector(const u8* raw_sector, const
}
// Apply volume when pushing sectors to SPU.
if (s_state.muted || g_settings.cdrom_mute_cd_audio)
if (s_state.muted || s_state.cdda_auto_pause_pending || g_settings.cdrom_mute_cd_audio)
return;
SPU::GeneratePendingSamples();
@ -3917,7 +3933,7 @@ void CDROM::CheckForSectorBufferReadComplete()
CanUseReadSpeedup() && g_settings.cdrom_read_speedup == 0)
{
const TickCount remaining_time = s_state.drive_event.GetTicksUntilNextExecution();
const TickCount instant_ticks = System::ScaleTicksToOverclock(g_settings.cdrom_max_speedup_cycles);
const TickCount instant_ticks = System::ScaleTicksToOverclock(g_settings.cdrom_max_read_speedup_cycles);
if (remaining_time > instant_ticks)
s_state.drive_event.Schedule(instant_ticks);
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "cpu_core.h"
@ -224,6 +224,7 @@ void CPU::Reset()
g_state.downcount = 0;
g_state.pending_ticks = 0;
g_state.gte_completion_tick = 0;
g_state.muldiv_completion_tick = 0;
}
bool CPU::DoState(StateWrapper& sw)
@ -231,6 +232,7 @@ bool CPU::DoState(StateWrapper& sw)
sw.Do(&g_state.pending_ticks);
sw.Do(&g_state.downcount);
sw.DoEx(&g_state.gte_completion_tick, 78, static_cast<u32>(0));
sw.DoEx(&g_state.muldiv_completion_tick, 80, static_cast<u32>(0));
sw.DoArray(g_state.regs.r, static_cast<u32>(Reg::count));
sw.Do(&g_state.pc);
sw.Do(&g_state.npc);
@ -301,6 +303,7 @@ bool CPU::DoState(StateWrapper& sw)
((g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter) ? CPUExecutionMode::CachedInterpreter :
g_settings.cpu_execution_mode);
g_state.gte_completion_tick = 0;
g_state.muldiv_completion_tick = 0;
UpdateMemoryPointers();
UpdateDebugDispatcherFlag();
}
@ -811,14 +814,26 @@ const std::array<CPU::DebuggerRegisterListEntry, CPU::NUM_DEBUGGER_REGISTER_LIST
{"ZSF4", &CPU::g_state.gte_regs.r32[62]},
{"FLAG", &CPU::g_state.gte_regs.r32[63]}}};
ALWAYS_INLINE static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32 new_value)
ALWAYS_INLINE static constexpr bool AddOverflow(u32 old_value, u32 add_value, u32* new_value)
{
return (((new_value ^ old_value) & (new_value ^ add_value)) & UINT32_C(0x80000000)) != 0;
#if defined(__clang__) || defined(__GNUC__)
return __builtin_add_overflow(static_cast<s32>(old_value), static_cast<s32>(add_value),
reinterpret_cast<s32*>(new_value));
#else
*new_value = old_value + add_value;
return (((*new_value ^ old_value) & (*new_value ^ add_value)) & UINT32_C(0x80000000)) != 0;
#endif
}
ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32 new_value)
ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u32* new_value)
{
return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0;
#if defined(__clang__) || defined(__GNUC__)
return __builtin_sub_overflow(static_cast<s32>(old_value), static_cast<s32>(sub_value),
reinterpret_cast<s32*>(new_value));
#else
*new_value = old_value - sub_value;
return (((*new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0;
#endif
}
void CPU::DisassembleAndPrint(u32 addr, bool regs, const char* prefix)
@ -999,8 +1014,8 @@ restart_instruction:
{
const u32 rsVal = ReadReg(inst.r.rs);
const u32 rtVal = ReadReg(inst.r.rt);
const u32 rdVal = rsVal + rtVal;
if (AddOverflow(rsVal, rtVal, rdVal))
u32 rdVal;
if (AddOverflow(rsVal, rtVal, &rdVal)) [[unlikely]]
{
RaiseException(Exception::Ov);
return;
@ -1033,8 +1048,8 @@ restart_instruction:
{
const u32 rsVal = ReadReg(inst.r.rs);
const u32 rtVal = ReadReg(inst.r.rt);
const u32 rdVal = rsVal - rtVal;
if (SubOverflow(rsVal, rtVal, rdVal))
u32 rdVal;
if (SubOverflow(rsVal, rtVal, &rdVal)) [[unlikely]]
{
RaiseException(Exception::Ov);
return;
@ -1088,6 +1103,8 @@ restart_instruction:
const u32 value = g_state.regs.hi;
WriteReg(inst.r.rd, value);
StallUntilMulDivComplete();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MOVE(static_cast<u32>(inst.r.rd.GetValue()), static_cast<u32>(Reg::hi), value);
}
@ -1098,6 +1115,8 @@ restart_instruction:
const u32 value = ReadReg(inst.r.rs);
g_state.regs.hi = value;
StallUntilMulDivComplete();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MOVE(static_cast<u32>(Reg::hi), static_cast<u32>(inst.r.rs.GetValue()), value);
}
@ -1108,6 +1127,8 @@ restart_instruction:
const u32 value = g_state.regs.lo;
WriteReg(inst.r.rd, value);
StallUntilMulDivComplete();
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MOVE(static_cast<u32>(inst.r.rd.GetValue()), static_cast<u32>(Reg::lo), value);
}
@ -1118,6 +1139,8 @@ restart_instruction:
const u32 value = ReadReg(inst.r.rs);
g_state.regs.lo = value;
StallUntilMulDivComplete();
if constexpr (pgxp_mode == PGXPMode::CPU)
PGXP::CPU_MOVE(static_cast<u32>(Reg::lo), static_cast<u32>(inst.r.rs.GetValue()), value);
}
@ -1133,6 +1156,9 @@ restart_instruction:
g_state.regs.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result);
StallUntilMulDivComplete();
AddMulDivTicks(GetMultTicks(static_cast<s32>(lhs)));
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MULT(inst, lhs, rhs);
}
@ -1147,6 +1173,9 @@ restart_instruction:
g_state.regs.hi = Truncate32(result >> 32);
g_state.regs.lo = Truncate32(result);
StallUntilMulDivComplete();
AddMulDivTicks(GetMultTicks(lhs));
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_MULTU(inst, lhs, rhs);
}
@ -1175,6 +1204,9 @@ restart_instruction:
g_state.regs.hi = static_cast<u32>(num % denom);
}
StallUntilMulDivComplete();
AddMulDivTicks(GetDivTicks());
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_DIV(inst, num, denom);
}
@ -1197,6 +1229,9 @@ restart_instruction:
g_state.regs.hi = num % denom;
}
StallUntilMulDivComplete();
AddMulDivTicks(GetDivTicks());
if constexpr (pgxp_mode >= PGXPMode::CPU)
PGXP::CPU_DIVU(inst, num, denom);
}
@ -1296,8 +1331,8 @@ restart_instruction:
{
const u32 rsVal = ReadReg(inst.i.rs);
const u32 imm = inst.i.imm_sext32();
const u32 rtVal = rsVal + imm;
if (AddOverflow(rsVal, imm, rtVal))
u32 rtVal;
if (AddOverflow(rsVal, imm, &rtVal)) [[unlikely]]
{
RaiseException(Exception::Ov);
return;
@ -1844,7 +1879,7 @@ restart_instruction:
case InstructionOp::cop2:
{
if (!g_state.cop0_regs.sr.CE2)
if (!g_state.cop0_regs.sr.CE2) [[unlikely]]
{
WARNING_LOG("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
@ -1916,7 +1951,7 @@ restart_instruction:
case InstructionOp::lwc2:
{
if (!g_state.cop0_regs.sr.CE2)
if (!g_state.cop0_regs.sr.CE2) [[unlikely]]
{
WARNING_LOG("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
@ -1937,7 +1972,7 @@ restart_instruction:
case InstructionOp::swc2:
{
if (!g_state.cop0_regs.sr.CE2)
if (!g_state.cop0_regs.sr.CE2) [[unlikely]]
{
WARNING_LOG("Coprocessor 2 not enabled");
RaiseException(Exception::CpU);
@ -1969,6 +2004,7 @@ restart_instruction:
break;
// everything else is reserved/invalid
[[unlikely]]
default:
{
u32 ram_value;
@ -2350,17 +2386,20 @@ ALWAYS_INLINE_RELEASE bool CPU::CheckBreakpointList(BreakpointType type, Virtual
{
System::PauseSystem(true);
TinyString msg;
if (bp.auto_clear)
{
Host::ReportDebuggerMessage(fmt::format("Stopped execution at 0x{:08X}.", pc));
msg.format("Stopped execution at 0x{:08X}.", pc);
Host::ReportDebuggerMessage(msg);
bplist.erase(bplist.begin() + i);
count--;
UpdateDebugDispatcherFlag();
}
else
{
Host::ReportDebuggerMessage(fmt::format("Hit {} breakpoint {} at 0x{:08X}, Hit Count {}.",
GetBreakpointTypeName(type), bp.number, address, bp.hit_count));
msg.format("Hit {} breakpoint {} at 0x{:08X}, Hit Count {}.", GetBreakpointTypeName(type), bp.number, address,
bp.hit_count);
Host::ReportDebuggerMessage(msg);
i++;
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -76,6 +76,7 @@ struct ALIGN_TO_CACHE_LINE State
u32 downcount = 0;
u32 pending_ticks = 0;
u32 gte_completion_tick = 0;
u32 muldiv_completion_tick = 0;
Registers regs = {};
Cop0Registers cop0_regs = {};
@ -155,6 +156,8 @@ ALWAYS_INLINE static void ResetPendingTicks()
{
g_state.gte_completion_tick =
(g_state.pending_ticks < g_state.gte_completion_tick) ? (g_state.gte_completion_tick - g_state.pending_ticks) : 0;
g_state.muldiv_completion_tick =
(g_state.pending_ticks < g_state.muldiv_completion_tick) ? (g_state.muldiv_completion_tick - g_state.pending_ticks) : 0;
g_state.pending_ticks = 0;
}
ALWAYS_INLINE static void AddPendingTicks(TickCount ticks)

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -132,6 +132,36 @@ ALWAYS_INLINE static void StallUntilGTEComplete()
(g_state.gte_completion_tick > g_state.pending_ticks) ? g_state.gte_completion_tick : g_state.pending_ticks;
}
ALWAYS_INLINE static void AddMulDivTicks(TickCount ticks)
{
g_state.muldiv_completion_tick = g_state.pending_ticks + ticks;
}
ALWAYS_INLINE static void StallUntilMulDivComplete()
{
g_state.pending_ticks =
(g_state.muldiv_completion_tick > g_state.pending_ticks) ? g_state.muldiv_completion_tick : g_state.pending_ticks;
}
ALWAYS_INLINE static constexpr TickCount GetMultTicks(s32 rs)
{
// Subtract one because of the instruction cycle.
if (rs < 0)
return (rs >= -2048) ? (6 - 1) : ((rs >= -1048576) ? (9 - 1) : (13 - 1));
else
return (rs < 0x800) ? (6 - 1) : ((rs < 0x100000) ? (9 - 1) : (13 - 1));
}
ALWAYS_INLINE static constexpr TickCount GetMultTicks(u32 rs)
{
return (rs < 0x800) ? (6 - 1) : ((rs < 0x100000) ? (9 - 1) : (13 - 1));
}
ALWAYS_INLINE static constexpr TickCount GetDivTicks()
{
return (36 - 1);
}
// kernel call interception
void HandleA0Syscall();
void HandleB0Syscall();

View file

@ -555,7 +555,7 @@ void CPU::FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction
{
const GTE::Instruction gi{inst.bits};
const GTEInstructionTable& t = s_gte_instructions[gi.command];
dest->append(t.name);
dest->assign(t.name);
if (t.sf && gi.sf)
dest->append(" sf");

View file

@ -613,7 +613,7 @@ bool CPU::PGXP::GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, i
{
*out_x = TruncateVertexPosition(vert->x) + static_cast<float>(xOffs);
*out_y = TruncateVertexPosition(vert->y) + static_cast<float>(yOffs);
*out_w = vert->z / 32768.0f;
*out_w = vert->z / static_cast<float>(GTE::MAX_Z);
#ifdef LOG_LOOKUPS
GL_INS_FMT("0x{:08X} {},{} => {},{} ({},{},{}) ({},{})", addr, x, y, *out_x, *out_y,
@ -635,7 +635,7 @@ bool CPU::PGXP::GetPreciseVertex(u32 addr, u32 value, int x, int y, int xOffs, i
{
*out_x = TruncateVertexPosition(vert->x) + static_cast<float>(xOffs);
*out_y = TruncateVertexPosition(vert->y) + static_cast<float>(yOffs);
*out_w = vert->z / 32768.0f;
*out_w = vert->z / static_cast<float>(GTE::MAX_Z);
if (IsWithinTolerance(*out_x, *out_y, x, y))
return false;

View file

@ -1314,7 +1314,7 @@ void CPU::Recompiler::Recompiler::CompileInstruction()
}
break;
case InstructionOp::lwc2: CompileLoadStoreTemplate(&Recompiler::Compile_lwc2, MemoryAccessSize::Word, false, false, TF_READS_S | TF_LOAD_DELAY); break;
case InstructionOp::lwc2: CompileLoadStoreTemplate(&Recompiler::Compile_lwc2, MemoryAccessSize::Word, false, false, TF_READS_S); break;
case InstructionOp::swc2: CompileLoadStoreTemplate(&Recompiler::Compile_swc2, MemoryAccessSize::Word, true, false, TF_GTE_STALL | TF_READS_S); SpecExec_swc2(); break;
// swc0/lwc0/cop1/cop3 are essentially no-ops
@ -1703,6 +1703,12 @@ void CPU::Recompiler::Recompiler::CompileLoadStoreTemplate(
}
}
// when not using fastmem, flush GTE completion cycle
// otherwise we end up consuming more cycles, because we're only counting a single cycle for loads
// and ram loads would have normally used up all the cycles the GTE was busy for
if (!use_fastmem && !store)
Flush(FLUSH_GTE_DONE_CYCLE);
(this->*func)(cf, size, sign, use_fastmem, addr);
if (store && !m_block_ended && !m_current_instruction_branch_delay_slot && spec_addr.has_value() &&

File diff suppressed because it is too large Load diff

View file

@ -9,11 +9,16 @@
#include <functional>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
class SmallStringBase;
class GPUSwapChain;
class GPUTexture;
struct GPUSettings;
namespace FullscreenUI {
@ -22,6 +27,7 @@ bool IsInitialized();
bool HasActiveWindow();
void CheckForConfigChanges(const GPUSettings& old_settings);
void OnSystemStarting();
void OnSystemPaused();
void OnSystemResumed();
void OnSystemDestroyed();
void OnRunningGameChanged(const std::string& path, const std::string& serial, const std::string& title, GameHash hash);
@ -39,8 +45,32 @@ void UpdateLoadingScreen(std::string_view image, std::string_view message, s32 p
s32 progress_value = -1);
void CloseLoadingScreen();
void SetTheme();
#ifndef __ANDROID__
static constexpr float SHORT_TRANSITION_TIME = 0.08f;
static constexpr float DEFAULT_TRANSITION_TIME = 0.15f;
enum class TransitionState : u8
{
Inactive,
Starting,
Active,
};
using TransitionStartCallback = std::function<void()>;
void BeginTransition(TransitionStartCallback func, float time = DEFAULT_TRANSITION_TIME);
void BeginTransition(float time, TransitionStartCallback func);
void CancelTransition();
bool IsTransitionActive();
TransitionState GetTransitionState();
GPUTexture* GetTransitionRenderTexture(GPUSwapChain* swap_chain);
void RenderTransitionBlend(GPUSwapChain* swap_chain);
std::vector<std::string_view> GetThemeNames();
std::span<const char* const> GetThemeConfigNames();
void OpenPauseMenu();
void OpenCheatsMenu();
void OpenDiscChangeMenu();
@ -114,6 +144,9 @@ namespace Host {
#ifndef __ANDROID__
/// Requests settings reset.
void RequestResetSettings(bool system, bool controller);
/// Requests shut down and exit of the hosting application. This may not actually exit,
/// if the user cancels the shutdown confirmation.
void RequestExitApplication(bool allow_confirm);

View file

@ -40,7 +40,7 @@ namespace GameDatabase {
enum : u32
{
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
GAME_DATABASE_CACHE_VERSION = 21,
GAME_DATABASE_CACHE_VERSION = 25,
};
static const Entry* GetEntryForId(std::string_view code);
@ -74,21 +74,24 @@ static constexpr const std::array<const char*, static_cast<size_t>(Compatibility
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "No Issues", "CompatibilityRating"),
}};
static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCount)> s_trait_names = {{
static constexpr const std::array s_trait_names = {
"ForceInterpreter",
"ForceSoftwareRenderer",
"ForceSoftwareRendererForReadbacks",
"ForceRoundTextureCoordinates",
"ForceAccurateBlending",
"ForceShaderBlending",
"ForceFullTrueColor",
"ForceDeinterlacing",
"ForceFullBoot",
"DisableAutoAnalogMode",
"DisableMultitap",
"DisableTrueColor",
"DisableFullTrueColor",
"DisableUpscaling",
"DisableTextureFiltering",
"DisableSpriteTextureFiltering",
"DisableScaledDithering",
"DisableScaledInterlacing",
"DisableWidescreen",
"DisablePGXP",
"DisablePGXPCulling",
@ -101,23 +104,27 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
"ForceRecompilerICache",
"ForceCDROMSubQSkew",
"IsLibCryptProtected",
}};
};
static_assert(s_trait_names.size() == static_cast<size_t>(Trait::MaxCount));
static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCount)> s_trait_display_names = {{
static constexpr const std::array s_trait_display_names = {
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Interpreter", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Software Renderer", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Software Renderer For Readbacks", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Round Texture Coordinates", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Accurate Blending", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Shader Blending", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Full True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Deinterlacing", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Full Boot", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Automatic Analog Mode", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Multitap", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Full True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Upscaling", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Texture Filtering", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Sprite Texture Filtering", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Scaled Dithering", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Scaled Interlacing", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Widescreen", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable PGXP", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable PGXP Culling", "GameDatabase::Trait"),
@ -130,13 +137,15 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Recompiler ICache", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force CD-ROM SubQ Skew", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Is LibCrypt Protected", "GameDatabase::Trait"),
}};
};
static_assert(s_trait_display_names.size() == static_cast<size_t>(Trait::MaxCount));
static constexpr std::array<const char*, static_cast<size_t>(Language::MaxCount)> s_language_names = {{
static constexpr std::array s_language_names = {
"Catalan", "Chinese", "Czech", "Danish", "Dutch", "English", "Finnish", "French",
"German", "Greek", "Hebrew", "Iranian", "Italian", "Japanese", "Korean", "Norwegian",
"Polish", "Portuguese", "Russian", "Spanish", "Swedish", "Turkish",
}};
};
static_assert(s_language_names.size() == static_cast<size_t>(Language::MaxCount));
static constexpr const char* GAMEDB_YAML_FILENAME = "gamedb.yaml";
static constexpr const char* DISCDB_YAML_FILENAME = "discdb.yaml";
@ -383,6 +392,18 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
if (display_osd_messages)
INFO_LOG("GameDB: DMA halt ticks set to {}.", settings.dma_halt_ticks);
}
if (cdrom_max_seek_speedup_cycles.has_value() && g_settings.cdrom_seek_speedup == 0)
{
settings.cdrom_max_seek_speedup_cycles = cdrom_max_seek_speedup_cycles.value();
if (display_osd_messages)
INFO_LOG("GameDB: CDROM maximum seek speedup cycles set to {}.", settings.cdrom_max_seek_speedup_cycles);
}
if (cdrom_max_read_speedup_cycles.has_value() && g_settings.cdrom_read_speedup == 0)
{
settings.cdrom_max_read_speedup_cycles = cdrom_max_read_speedup_cycles.value();
if (display_osd_messages)
INFO_LOG("GameDB: CDROM maximum read speedup cycles set to {}.", settings.cdrom_max_read_speedup_cycles);
}
if (gpu_fifo_size.has_value())
{
settings.gpu_fifo_size = gpu_fifo_size.value();
@ -487,14 +508,6 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
settings.gpu_force_round_texcoords = true;
}
if (HasTrait(Trait::ForceAccurateBlending))
{
if (display_osd_messages && !settings.IsUsingSoftwareRenderer() && !settings.gpu_accurate_blending)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Accurate blending enabled."));
settings.gpu_accurate_blending = true;
}
if (HasTrait(Trait::ForceDeinterlacing))
{
const DisplayDeinterlacingMode new_mode = display_deinterlacing_mode.value_or(
@ -525,12 +538,41 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
}
}
if (HasTrait(Trait::DisableTrueColor))
if (HasTrait(Trait::DisableTrueColor) || HasTrait(Trait::DisableFullTrueColor) ||
HasTrait(Trait::DisableScaledDithering) || HasTrait(Trait::ForceShaderBlending) ||
HasTrait(Trait::ForceFullTrueColor))
{
if (display_osd_messages && settings.gpu_true_color)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "True color disabled."));
// Note: The order these are applied matters.
const GPUDitheringMode old_mode = settings.gpu_dithering_mode;
if (HasTrait(Trait::DisableTrueColor) && settings.IsUsingTrueColor())
{
settings.gpu_dithering_mode = GPUDitheringMode::Scaled;
}
if (HasTrait(Trait::DisableScaledDithering) && settings.IsUsingDithering())
{
settings.gpu_dithering_mode =
(settings.IsUsingShaderBlending() ? GPUDitheringMode::UnscaledShaderBlend : GPUDitheringMode::Unscaled);
}
if (HasTrait(Trait::ForceShaderBlending) && settings.IsUsingDithering() && !settings.IsUsingShaderBlending())
{
settings.gpu_dithering_mode = (settings.gpu_dithering_mode == GPUDitheringMode::Scaled) ?
GPUDitheringMode::ScaledShaderBlend :
GPUDitheringMode::UnscaledShaderBlend;
}
if (HasTrait(Trait::ForceFullTrueColor) && settings.gpu_dithering_mode == GPUDitheringMode::TrueColor)
{
settings.gpu_dithering_mode = GPUDitheringMode::TrueColorFull;
}
if (HasTrait(Trait::DisableFullTrueColor) && settings.gpu_dithering_mode == GPUDitheringMode::TrueColorFull)
{
settings.gpu_dithering_mode = GPUDitheringMode::TrueColor;
}
settings.gpu_true_color = false;
if (display_osd_messages && settings.gpu_dithering_mode != old_mode)
{
APPEND_MESSAGE_FMT(TRANSLATE_FS("GameDatabase", "Dithering set to {}."),
Settings::GetGPUDitheringModeDisplayName(settings.gpu_dithering_mode));
}
}
if (HasTrait(Trait::DisableUpscaling))
@ -563,12 +605,15 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest;
}
if (HasTrait(Trait::DisableScaledDithering))
if (HasTrait(Trait::DisableScaledInterlacing))
{
if (display_osd_messages && settings.gpu_scaled_dithering)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Scaled dithering."));
if (display_osd_messages && settings.gpu_scaled_interlacing &&
settings.display_deinterlacing_mode != DisplayDeinterlacingMode::Progressive)
{
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Scaled interlacing disabled."));
}
settings.gpu_scaled_dithering = false;
settings.gpu_scaled_interlacing = false;
}
if (HasTrait(Trait::DisableWidescreen))
@ -957,7 +1002,8 @@ bool GameDatabase::LoadFromCache()
!reader.ReadOptionalT(&entry.display_line_start_offset) ||
!reader.ReadOptionalT(&entry.display_line_end_offset) || !reader.ReadOptionalT(&entry.display_crop_mode) ||
!reader.ReadOptionalT(&entry.display_deinterlacing_mode) || !reader.ReadOptionalT(&entry.dma_max_slice_ticks) ||
!reader.ReadOptionalT(&entry.dma_halt_ticks) || !reader.ReadOptionalT(&entry.gpu_fifo_size) ||
!reader.ReadOptionalT(&entry.dma_halt_ticks) || !reader.ReadOptionalT(&entry.cdrom_max_seek_speedup_cycles) ||
!reader.ReadOptionalT(&entry.cdrom_max_read_speedup_cycles) || !reader.ReadOptionalT(&entry.gpu_fifo_size) ||
!reader.ReadOptionalT(&entry.gpu_max_run_ahead) || !reader.ReadOptionalT(&entry.gpu_pgxp_tolerance) ||
!reader.ReadOptionalT(&entry.gpu_pgxp_depth_threshold) ||
!reader.ReadOptionalT(&entry.gpu_pgxp_preserve_proj_fp) || !reader.ReadOptionalT(&entry.gpu_line_detect_mode) ||
@ -1075,6 +1121,8 @@ bool GameDatabase::SaveToCache()
writer.WriteOptionalT(entry.display_deinterlacing_mode);
writer.WriteOptionalT(entry.dma_max_slice_ticks);
writer.WriteOptionalT(entry.dma_halt_ticks);
writer.WriteOptionalT(entry.cdrom_max_seek_speedup_cycles);
writer.WriteOptionalT(entry.cdrom_max_read_speedup_cycles);
writer.WriteOptionalT(entry.gpu_fifo_size);
writer.WriteOptionalT(entry.gpu_max_run_ahead);
writer.WriteOptionalT(entry.gpu_pgxp_tolerance);
@ -1172,6 +1220,38 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
{
GetStringFromObject(value, "name", &entry->title);
entry->supported_controllers = static_cast<u16>(~0u);
if (const ryml::ConstNodeRef controllers = value.find_child(to_csubstr("controllers"));
controllers.valid() && controllers.has_children())
{
bool first = true;
for (const ryml::ConstNodeRef& controller : controllers.cchildren())
{
const std::string_view controller_str = to_stringview(controller.val());
if (controller_str.empty())
{
WARNING_LOG("controller is not a string in {}", entry->serial);
return false;
}
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(controller_str);
if (!cinfo)
{
WARNING_LOG("Invalid controller type {} in {}", controller_str, entry->serial);
continue;
}
if (first)
{
entry->supported_controllers = 0;
first = false;
}
entry->supported_controllers |= (1u << static_cast<u16>(cinfo->type));
}
}
if (const ryml::ConstNodeRef metadata = value.find_child(to_csubstr("metadata")); metadata.valid())
{
GetStringFromObject(metadata, "genre", &entry->genre);
@ -1214,37 +1294,19 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
}
}
}
}
entry->supported_controllers = static_cast<u16>(~0u);
if (const ryml::ConstNodeRef controllers = value.find_child(to_csubstr("controllers"));
controllers.valid() && controllers.has_children())
{
bool first = true;
for (const ryml::ConstNodeRef& controller : controllers.cchildren())
if (const ryml::ConstNodeRef& multitap = metadata.find_child(to_csubstr("multitap")); multitap.valid())
{
const std::string_view controller_str = to_stringview(controller.val());
if (controller_str.empty())
if (const std::optional multitap_val = StringUtil::FromChars<bool>(to_stringview(multitap.val()));
multitap_val.has_value())
{
WARNING_LOG("controller is not a string in {}", entry->serial);
return false;
if (multitap_val.value())
entry->supported_controllers |= Entry::SUPPORTS_MULTITAP_BIT;
}
const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(controller_str);
if (!cinfo)
else
{
WARNING_LOG("Invalid controller type {} in {}", controller_str, entry->serial);
continue;
WARNING_LOG("Invalid multitap value in {}", entry->serial);
}
if (first)
{
entry->supported_controllers = 0;
first = false;
}
entry->supported_controllers |= (1u << static_cast<u16>(cinfo->type));
}
}
@ -1310,20 +1372,6 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
}
}
if (const ryml::ConstNodeRef& multitap = value.find_child(to_csubstr("multitap")); multitap.valid())
{
if (const std::optional multitap_val = StringUtil::FromChars<bool>(to_stringview(multitap.val()));
multitap_val.has_value())
{
if (multitap_val.value())
entry->supported_controllers |= Entry::SUPPORTS_MULTITAP_BIT;
}
else
{
WARNING_LOG("Invalid multitap value in {}", entry->serial);
}
}
if (const ryml::ConstNodeRef settings = value.find_child(to_csubstr("settings"));
settings.valid() && settings.has_children())
{
@ -1337,6 +1385,8 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
settings, "displayDeinterlacingMode", &Settings::ParseDisplayDeinterlacingMode);
entry->dma_max_slice_ticks = GetOptionalTFromObject<u32>(settings, "dmaMaxSliceTicks");
entry->dma_halt_ticks = GetOptionalTFromObject<u32>(settings, "dmaHaltTicks");
entry->cdrom_max_seek_speedup_cycles = GetOptionalTFromObject<u32>(settings, "cdromMaxSeekSpeedupCycles");
entry->cdrom_max_read_speedup_cycles = GetOptionalTFromObject<u32>(settings, "cdromMaxReadSpeedupCycles");
entry->gpu_fifo_size = GetOptionalTFromObject<u32>(settings, "gpuFIFOSize");
entry->gpu_max_run_ahead = GetOptionalTFromObject<u32>(settings, "gpuMaxRunAhead");
entry->gpu_pgxp_tolerance = GetOptionalTFromObject<float>(settings, "gpuPGXPTolerance");

View file

@ -37,16 +37,19 @@ enum class Trait : u32
ForceSoftwareRenderer,
ForceSoftwareRendererForReadbacks,
ForceRoundUpscaledTextureCoordinates,
ForceAccurateBlending,
ForceShaderBlending,
ForceFullTrueColor,
ForceDeinterlacing,
ForceFullBoot,
DisableAutoAnalogMode,
DisableMultitap,
DisableTrueColor,
DisableFullTrueColor,
DisableUpscaling,
DisableTextureFiltering,
DisableSpriteTextureFiltering,
DisableScaledDithering,
DisableScaledInterlacing,
DisableWidescreen,
DisablePGXP,
DisablePGXPCulling,
@ -120,6 +123,8 @@ struct Entry
std::optional<GPULineDetectMode> gpu_line_detect_mode;
std::optional<u32> dma_max_slice_ticks;
std::optional<u32> dma_halt_ticks;
std::optional<u32> cdrom_max_seek_speedup_cycles;
std::optional<u32> cdrom_max_read_speedup_cycles;
std::optional<u32> gpu_fifo_size;
std::optional<u32> gpu_max_run_ahead;
std::optional<float> gpu_pgxp_tolerance;

View file

@ -92,6 +92,7 @@ static bool ShouldLoadAchievementsProgress();
static bool GetExeListEntry(const std::string& path, Entry* entry);
static bool GetPsfListEntry(const std::string& path, Entry* entry);
static bool GetDiscListEntry(const std::string& path, Entry* entry);
static void MakeInvalidEntry(Entry* entry);
static void ApplyCustomAttributes(const std::string& path, Entry* entry,
const INISettingsInterface& custom_attributes_ini);
@ -109,7 +110,7 @@ static void ScanDirectory(const char* path, bool recursive, bool only_cache,
static bool AddFileFromCache(const std::string& path, std::time_t timestamp, const PlayedTimeMap& played_time_map,
const INISettingsInterface& custom_attributes_ini,
const Achievements::ProgressDatabase& achievements_progress);
static bool ScanFile(std::string path, std::time_t timestamp, std::unique_lock<std::recursive_mutex>& lock,
static void ScanFile(std::string path, std::time_t timestamp, std::unique_lock<std::recursive_mutex>& lock,
const PlayedTimeMap& played_time_map, const INISettingsInterface& custom_attributes_ini,
const Achievements::ProgressDatabase& achievements_progress, BinaryFileWriter& cache_writer);
@ -144,7 +145,7 @@ static bool s_game_list_loaded = false;
const char* GameList::GetEntryTypeName(EntryType type)
{
static std::array<const char*, static_cast<int>(EntryType::Count)> names = {{
static std::array<const char*, static_cast<int>(EntryType::MaxCount)> names = {{
"Disc",
"DiscSet",
"PSExe",
@ -156,7 +157,7 @@ const char* GameList::GetEntryTypeName(EntryType type)
const char* GameList::GetEntryTypeDisplayName(EntryType type)
{
static std::array<const char*, static_cast<int>(EntryType::Count)> names = {{
static std::array<const char*, static_cast<int>(EntryType::MaxCount)> names = {{
TRANSLATE_DISAMBIG_NOOP("GameList", "Disc", "EntryType"),
TRANSLATE_DISAMBIG_NOOP("GameList", "Disc Set", "EntryType"),
TRANSLATE_DISAMBIG_NOOP("GameList", "PS-EXE", "EntryType"),
@ -177,7 +178,7 @@ bool GameList::IsScannableFilename(std::string_view path)
if (StringUtil::EndsWithNoCase(path, ".bin"))
return false;
return System::IsLoadablePath(path);
return (System::IsDiscPath(path) || System::IsExePath(path) || System::IsPsfPath(path));
}
bool GameList::ShouldLoadAchievementsProgress()
@ -369,6 +370,32 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry)
return true;
}
void GameList::MakeInvalidEntry(Entry* entry)
{
entry->type = EntryType::MaxCount;
entry->region = DiscRegion::Other;
entry->disc_set_index = -1;
entry->disc_set_member = false;
entry->has_custom_title = false;
entry->has_custom_region = false;
entry->custom_language = GameDatabase::Language::MaxCount;
entry->path = {};
entry->serial = {};
entry->title = {};
entry->disc_set_name = {};
entry->dbentry = nullptr;
entry->hash = 0;
entry->file_size = 0;
entry->uncompressed_size = 0;
entry->last_modified_time = 0;
entry->last_played_time = 0;
entry->achievements_hash = {};
entry->achievements_game_id = 0;
entry->num_achievements = 0;
entry->unlocked_achievements = 0;
entry->unlocked_achievements_hc = 0;
}
bool GameList::PopulateEntryFromPath(const std::string& path, Entry* entry)
{
if (System::IsExePath(path))
@ -420,7 +447,7 @@ bool GameList::LoadEntriesFromCache(BinaryFileReader& reader)
!reader.ReadS64(&ge.file_size) || !reader.ReadU64(&ge.uncompressed_size) ||
!reader.ReadU64(reinterpret_cast<u64*>(&ge.last_modified_time)) || !reader.ReadS8(&ge.disc_set_index) ||
!reader.Read(ge.achievements_hash.data(), ge.achievements_hash.size()) ||
region >= static_cast<u8>(DiscRegion::Count) || type >= static_cast<u8>(EntryType::Count))
region >= static_cast<u8>(DiscRegion::Count) || type > static_cast<u8>(EntryType::MaxCount))
{
WARNING_LOG("Game list cache entry is corrupted");
return false;
@ -468,6 +495,8 @@ bool GameList::LoadOrInitializeCache(std::FILE* fp, bool invalidate_cache)
WARNING_LOG("Initializing game list cache.");
s_cache_map.clear();
if (!fp)
return false;
// Truncate file, and re-write header.
Error error;
@ -501,7 +530,7 @@ void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache,
const Achievements::ProgressDatabase& achievements_progress,
BinaryFileWriter& cache_writer, ProgressCallback* progress)
{
INFO_LOG("Scanning {}{}", path, recursive ? " (recursively)" : "");
VERBOSE_LOG("Scanning {}{}", path, recursive ? " (recursively)" : "");
progress->SetStatusText(SmallString::from_format(TRANSLATE_FS("GameList", "Scanning directory '{}'..."), path));
@ -522,11 +551,8 @@ void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache,
{
files_scanned++;
if (progress->IsCancelled() || !GameList::IsScannableFilename(ffd.FileName) ||
IsPathExcluded(excluded_paths, ffd.FileName))
{
if (progress->IsCancelled() || !IsScannableFilename(ffd.FileName) || IsPathExcluded(excluded_paths, ffd.FileName))
continue;
}
std::unique_lock lock(s_mutex);
if (GetEntryForPath(ffd.FileName) ||
@ -559,6 +585,10 @@ bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp,
return false;
}
// don't add invalid entries to the list, but don't scan them either
if (!entry.IsValid())
return true;
auto iter = played_time_map.find(entry.serial);
if (iter != played_time_map.end())
{
@ -570,20 +600,33 @@ bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp,
return true;
}
bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_lock<std::recursive_mutex>& lock,
void GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_lock<std::recursive_mutex>& lock,
const PlayedTimeMap& played_time_map, const INISettingsInterface& custom_attributes_ini,
const Achievements::ProgressDatabase& achievements_progress, BinaryFileWriter& cache_writer)
{
// don't block UI while scanning
lock.unlock();
DEV_LOG("Scanning '{}'...", path);
VERBOSE_LOG("Scanning '{}'...", path);
Entry entry;
if (!PopulateEntryFromPath(path, &entry))
if (PopulateEntryFromPath(path, &entry))
{
lock.lock();
return false;
const auto iter = played_time_map.find(entry.serial);
if (iter != played_time_map.end())
{
entry.last_played_time = iter->second.last_played_time;
entry.total_played_time = iter->second.total_played_time;
}
ApplyCustomAttributes(entry.path, &entry, custom_attributes_ini);
if (entry.IsDisc())
PopulateEntryAchievements(&entry, achievements_progress);
}
else
{
MakeInvalidEntry(&entry);
}
entry.path = std::move(path);
@ -592,20 +635,12 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc
if (cache_writer.IsOpen() && !WriteEntryToCache(&entry, cache_writer)) [[unlikely]]
WARNING_LOG("Failed to write entry '{}' to cache", entry.path);
const auto iter = played_time_map.find(entry.serial);
if (iter != played_time_map.end())
{
entry.last_played_time = iter->second.last_played_time;
entry.total_played_time = iter->second.total_played_time;
}
ApplyCustomAttributes(entry.path, &entry, custom_attributes_ini);
if (entry.IsDisc())
PopulateEntryAchievements(&entry, achievements_progress);
lock.lock();
// don't add invalid entries to the list
if (!entry.IsValid())
return;
// replace if present
auto it = std::find_if(s_entries.begin(), s_entries.end(),
[&entry](const Entry& existing_entry) { return (existing_entry.path == entry.path); });
@ -613,8 +648,6 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc
*it = std::move(entry);
else
s_entries.push_back(std::move(entry));
return true;
}
bool GameList::RescanCustomAttributesForPath(const std::string& path, const INISettingsInterface& custom_attributes_ini)

View file

@ -28,12 +28,12 @@ enum class EntryType : u8
PSExe,
Playlist,
PSF,
Count
MaxCount
};
struct Entry
{
EntryType type = EntryType::Disc;
EntryType type = EntryType::MaxCount;
DiscRegion region = DiscRegion::Other;
s8 disc_set_index = -1;
@ -69,6 +69,7 @@ struct Entry
TinyString GetReleaseDateString() const;
ALWAYS_INLINE bool IsValid() const { return (type < EntryType::MaxCount); }
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
ALWAYS_INLINE bool IsDiscSet() const { return (type == EntryType::DiscSet); }
ALWAYS_INLINE bool HasCustomLanguage() const { return (custom_language != GameDatabase::Language::MaxCount); }

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gdb_server.h"
@ -11,83 +11,71 @@
#include "common/log.h"
#include "common/small_string.h"
#include "common/string_util.h"
#include "common/thirdparty/SmallVector.h"
#include "util/sockets.h"
#include <functional>
#include <iomanip>
#include <map>
#include <optional>
#include <sstream>
#include <string>
LOG_CHANNEL(GDBProtocol);
LOG_CHANNEL(GDBServer);
namespace GDBProtocol {
namespace GDBServer {
namespace {
class ClientSocket final : public BufferedStreamSocket
{
public:
ClientSocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor);
~ClientSocket() override;
void OnSystemPaused();
void OnSystemResumed();
void SendReplyWithAck(std::string_view reply = std::string_view());
protected:
void OnConnected() override;
void OnDisconnected(const Error& error) override;
void OnRead() override;
private:
void SendPacket(std::string_view sv);
bool m_seen_resume = false;
};
} // namespace
static u8 ComputeChecksum(std::string_view str);
static bool Cmd$_questionMark(ClientSocket* client, std::string_view data);
static bool Cmd$g(ClientSocket* client, std::string_view data);
static bool Cmd$G(ClientSocket* client, std::string_view data);
static bool Cmd$H(ClientSocket* client, std::string_view data);
static bool Cmd$m(ClientSocket* client, std::string_view data);
static bool Cmd$M(ClientSocket* client, std::string_view data);
static bool Cmd$s(ClientSocket* client, std::string_view data);
template<bool add_breakpoint>
static bool Cmd$z(ClientSocket* client, std::string_view data);
static bool Cmd$vMustReplyEmpty(ClientSocket* client, std::string_view data);
static bool Cmd$qSupported(ClientSocket* client, std::string_view data);
static bool IsPacketAck(std::string_view data);
static bool IsPacketInterrupt(std::string_view data);
static bool IsPacketContinue(std::string_view data);
static bool IsPacketComplete(std::string_view data);
static std::string ProcessPacket(std::string_view data);
} // namespace GDBProtocol
static bool ProcessPacket(ClientSocket* socket, std::string_view data);
namespace GDBProtocol {
/// yikes, lots of stack space
using LargeReplyPacket = SmallStackString<768>;
static u8* GetMemoryPointer(PhysicalMemoryAddress address, u32 length)
{
auto region = Bus::GetMemoryRegionForAddress(address);
if (region)
{
u8* data = GetMemoryRegionPointer(*region);
if (data && (address + length <= GetMemoryRegionEnd(*region)))
{
return data + (address - GetMemoryRegionStart(*region));
}
}
return nullptr;
}
static u8 ComputeChecksum(std::string_view str)
{
u8 checksum = 0;
for (char c : str)
{
checksum = (checksum + c) % 256;
}
return checksum;
}
static std::optional<std::string_view> DeserializePacket(std::string_view in)
{
if ((in.size() < 4) || (in[0] != '$') || (in[in.size() - 3] != '#'))
{
return std::nullopt;
}
std::string_view data = in.substr(1, in.size() - 4);
u8 packetChecksum = StringUtil::FromChars<u8>(in.substr(in.size() - 2, 2), 16).value_or(0);
u8 computedChecksum = ComputeChecksum(data);
if (packetChecksum == computedChecksum)
{
return {data};
}
else
{
return std::nullopt;
}
}
static std::string SerializePacket(std::string_view in)
{
std::stringstream ss;
ss << '$' << in << '#' << TinyString::from_format("{:02x}", ComputeChecksum(in));
return ss.str();
}
/// Number of registers in GDB remote protocol for MIPS III.
static constexpr int NUM_GDB_REGISTERS = 73;
/// List of GDB remote protocol registers for MIPS III (excluding FP).
static const std::array<u32*, 38> REGISTERS{
static constexpr std::array<u32*, 38> REGISTERS{
&CPU::g_state.regs.r[0],
&CPU::g_state.regs.r[1],
&CPU::g_state.regs.r[2],
@ -129,50 +117,84 @@ static const std::array<u32*, 38> REGISTERS{
&CPU::g_state.pc,
};
/// Number of registers in GDB remote protocol for MIPS III.
constexpr int NUM_GDB_REGISTERS = 73;
/// List of all GDB remote protocol packets supported by us.
static constexpr std::pair<std::string_view, bool (*)(ClientSocket*, std::string_view)> COMMANDS[] = {
{"?", Cmd$_questionMark},
{"g", Cmd$g},
{"G", Cmd$G},
{"H", Cmd$H},
{"m", Cmd$m},
{"M", Cmd$M},
{"s", Cmd$s},
{"z", Cmd$z<false>},
{"Z", Cmd$z<true>},
{"vMustReplyEmpty", Cmd$vMustReplyEmpty},
{"qSupported", Cmd$qSupported},
};
static std::shared_ptr<ListenSocket> s_gdb_listen_socket;
static std::vector<std::shared_ptr<ClientSocket>> s_gdb_clients;
} // namespace GDBServer
u8 GDBServer::ComputeChecksum(std::string_view str)
{
u8 checksum = 0;
for (char c : str)
checksum = (checksum + c) % 256;
return checksum;
}
/// Get stop reason.
static std::optional<std::string> Cmd$_questionMark(std::string_view data)
bool GDBServer::Cmd$_questionMark(ClientSocket* client, std::string_view data)
{
return {"S02"};
client->SendReplyWithAck("S02");
return true;
}
/// Get general registers.
static std::optional<std::string> Cmd$g(std::string_view data)
bool GDBServer::Cmd$g(ClientSocket* client, std::string_view data)
{
std::stringstream ss;
LargeReplyPacket reply;
for (u32* reg : REGISTERS)
for (const u32* reg : REGISTERS)
{
// Data is in host order (little endian).
ss << StringUtil::EncodeHex(reinterpret_cast<u8*>(reg), 4);
reply.append_format("{:02x}{:02x}{:02x}{:02x}", *reg & 0xFFu, (*reg >> 8) & 0xFFu, (*reg >> 16) & 0xFFu,
(*reg >> 24));
}
// Pad with dummy data (FP registers stuff).
for (int i = 0; i < NUM_GDB_REGISTERS - static_cast<int>(REGISTERS.size()); i++)
{
ss << "00000000";
}
reply.append("00000000");
return {ss.str()};
client->SendReplyWithAck(reply);
return true;
}
/// Set general registers.
static std::optional<std::string> Cmd$G(std::string_view data)
bool GDBServer::Cmd$G(ClientSocket* client, std::string_view data)
{
if (data.size() == NUM_GDB_REGISTERS * 8)
{
int offset = 0;
size_t offset = 0;
for (u32* reg : REGISTERS)
{
// Data is in host order (little endian).
auto value = StringUtil::DecodeHex({data.data() + offset, 8});
if (value)
const std::string_view tex_value = data.substr(offset, 8);
std::array<u8, 4> le_value;
if (StringUtil::DecodeHex(le_value, tex_value) == 4)
{
*reg = *reinterpret_cast<u32*>(&(*value)[0]);
*reg = ZeroExtend32(le_value[0]) | (ZeroExtend32(le_value[1]) << 8) | (ZeroExtend32(le_value[2]) << 16) |
(ZeroExtend32(le_value[3]) << 16);
}
else
{
ERROR_LOG("Invalid register set value: {}", tex_value);
}
offset += 8;
}
}
@ -181,210 +203,241 @@ static std::optional<std::string> Cmd$G(std::string_view data)
ERROR_LOG("Wrong payload size for 'G' command, expected {} got {}", NUM_GDB_REGISTERS * 8, data.size());
}
return {""};
client->SendReplyWithAck();
return true;
}
/// Thread operations, ignored.
bool GDBServer::Cmd$H(ClientSocket* client, std::string_view data)
{
WARNING_LOG("Ignoring thread command '{}'", data);
client->SendReplyWithAck("OK");
return true;
}
/// Get memory.
static std::optional<std::string> Cmd$m(std::string_view data)
bool GDBServer::Cmd$m(ClientSocket* client, std::string_view data)
{
std::stringstream ss{std::string{data}};
std::string dataAddress, dataLength;
std::getline(ss, dataAddress, ',');
std::getline(ss, dataLength, '\0');
auto address = StringUtil::FromChars<VirtualMemoryAddress>(dataAddress, 16);
auto length = StringUtil::FromChars<u32>(dataLength, 16);
if (address && length)
// address,length
std::string_view caret = data;
std::optional<VirtualMemoryAddress> address;
std::optional<u32> length;
if (!(address = StringUtil::FromChars<VirtualMemoryAddress>(caret, 16, &caret)).has_value() || caret.empty() ||
caret[0] != ',' || !(length = StringUtil::FromChars<u32>(caret.substr(1), 16)).has_value())
{
PhysicalMemoryAddress phys_addr = *address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
u32 phys_length = *length;
u8* ptr_data = GetMemoryPointer(phys_addr, phys_length);
if (ptr_data)
{
return {StringUtil::EncodeHex(ptr_data, phys_length)};
}
ERROR_LOG("Invalid packet: {}", data);
return false;
}
return {"E00"};
// large enough for most requests
llvm::SmallVector<u8, 128> buffer;
buffer.resize_for_overwrite(length.value());
if (!CPU::SafeReadMemoryBytes(address.value(), buffer.data(), length.value()))
{
ERROR_LOG("Failed to read {} bytes from address 0x{:08X}", buffer.size(), address.value());
client->SendReplyWithAck("E00");
return true;
}
SmallString reply;
reply.append_hex(buffer.data(), buffer.size());
client->SendReplyWithAck(reply);
return true;
}
/// Set memory.
static std::optional<std::string> Cmd$M(std::string_view data)
bool GDBServer::Cmd$M(ClientSocket* client, std::string_view data)
{
std::stringstream ss{std::string{data}};
std::string dataAddress, dataLength, dataPayload;
std::getline(ss, dataAddress, ',');
std::getline(ss, dataLength, ':');
std::getline(ss, dataPayload, '\0');
auto address = StringUtil::FromChars<VirtualMemoryAddress>(dataAddress, 16);
auto length = StringUtil::FromChars<u32>(dataLength, 16);
auto payload = StringUtil::DecodeHex(dataPayload);
if (address && length && payload && (payload->size() == *length))
// address,length:data
std::string_view caret = data;
std::optional<VirtualMemoryAddress> address;
std::optional<u32> length;
if (!(address = StringUtil::FromChars<VirtualMemoryAddress>(caret, 16, &caret)).has_value() || caret.empty() ||
caret[0] != ',' || !(length = StringUtil::FromChars<u32>(caret.substr(1), 16, &caret)).has_value() ||
caret.empty() || caret[0] != ':')
{
u32 phys_addr = *address & CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
u32 phys_length = *length;
ERROR_LOG("Invalid packet: {}", data);
return false;
}
u8* ptr_data = GetMemoryPointer(phys_addr, phys_length);
if (ptr_data)
// remove ':'
caret = caret.substr(1);
if (length.value() != (caret.size() / 2))
{
ERROR_LOG("Invalid length in packet {}", data);
return false;
}
// large enough for most requests
llvm::SmallVector<u8, 128> buffer;
buffer.resize_for_overwrite(length.value());
if (!StringUtil::DecodeHex(buffer, caret))
{
ERROR_LOG("Invalid hex in packet {}", data);
return false;
}
if (!CPU::SafeWriteMemoryBytes(address.value(), buffer))
{
ERROR_LOG("Failed to write {} bytes to {}", buffer.size(), address.value());
client->SendReplyWithAck("E00");
return true;
}
client->SendReplyWithAck("OK");
return true;
}
/// Single step.
bool GDBServer::Cmd$s(ClientSocket* client, std::string_view data)
{
System::SingleStepCPU();
client->SendReplyWithAck("OK");
return true;
}
/// Remove hardware breakpoint (z).
/// Insert hardware breakpoint (Z).
template<bool add_breakpoint>
bool GDBServer::Cmd$z(ClientSocket* client, std::string_view data)
{
std::string_view caret = data;
std::optional<u32> bptype;
std::optional<VirtualMemoryAddress> bpaddr;
// type,addr
if (!(bptype = StringUtil::FromChars<u32>(caret, 10, &caret)) || caret.empty() || caret[0] != ',' ||
!(bpaddr = StringUtil::FromChars<VirtualMemoryAddress>(caret.substr(1), 16)).has_value())
{
ERROR_LOG("Invalid {} hw breakpoint packet: {}", add_breakpoint ? "add" : "remove", data);
return false;
}
if (bptype.value() == 0 || bptype.value() == 1) // software/hardware breakpoint
{
if constexpr (add_breakpoint)
CPU::AddBreakpoint(CPU::BreakpointType::Execute, bpaddr.value());
else
CPU::RemoveBreakpoint(CPU::BreakpointType::Execute, bpaddr.value());
client->SendReplyWithAck("OK");
return true;
}
else if (bptype.value() == 2) // write breakpoint
{
if constexpr (add_breakpoint)
CPU::AddBreakpoint(CPU::BreakpointType::Write, bpaddr.value());
else
CPU::RemoveBreakpoint(CPU::BreakpointType::Write, bpaddr.value());
client->SendReplyWithAck("OK");
return true;
}
else if (bptype.value() == 3) // read breakpoint
{
if constexpr (add_breakpoint)
CPU::AddBreakpoint(CPU::BreakpointType::Read, bpaddr.value());
else
CPU::RemoveBreakpoint(CPU::BreakpointType::Read, bpaddr.value());
client->SendReplyWithAck("OK");
return true;
}
else if (bptype.value() == 4) // read+write breakpoint
{
if constexpr (add_breakpoint)
{
memcpy(ptr_data, payload->data(), phys_length);
return {"OK"};
CPU::AddBreakpoint(CPU::BreakpointType::Read, bpaddr.value());
CPU::AddBreakpoint(CPU::BreakpointType::Write, bpaddr.value());
}
else
{
CPU::RemoveBreakpoint(CPU::BreakpointType::Read, bpaddr.value());
CPU::RemoveBreakpoint(CPU::BreakpointType::Write, bpaddr.value());
}
}
return {"E00"};
}
/// Remove hardware breakpoint.
static std::optional<std::string> Cmd$z1(std::string_view data)
{
auto address = StringUtil::FromChars<VirtualMemoryAddress>(data, 16);
if (address)
{
CPU::RemoveBreakpoint(CPU::BreakpointType::Execute, *address);
return {"OK"};
client->SendReplyWithAck("OK");
return true;
}
else
{
return std::nullopt;
ERROR_LOG("Unknown breakpoint type {}", bptype.value());
return false;
}
}
/// Insert hardware breakpoint.
static std::optional<std::string> Cmd$Z1(std::string_view data)
template bool GDBServer::Cmd$z<false>(ClientSocket* client, std::string_view data);
template bool GDBServer::Cmd$z<true>(ClientSocket* client, std::string_view data);
bool GDBServer::Cmd$vMustReplyEmpty(ClientSocket* client, std::string_view data)
{
auto address = StringUtil::FromChars<VirtualMemoryAddress>(data, 16);
if (address)
{
CPU::AddBreakpoint(CPU::BreakpointType::Execute, *address, false);
return {"OK"};
}
else
{
return std::nullopt;
}
client->SendReplyWithAck();
return true;
}
static std::optional<std::string> Cmd$vMustReplyEmpty(std::string_view data)
bool GDBServer::Cmd$qSupported(ClientSocket* client, std::string_view data)
{
return {""};
client->SendReplyWithAck();
return true;
}
static std::optional<std::string> Cmd$qSupported(std::string_view data)
bool GDBServer::IsPacketAck(std::string_view data)
{
return {""};
DebugAssert(data.size() >= 1);
return (data[0] == '+' || data[0] == '-');
}
/// List of all GDB remote protocol packets supported by us.
static const std::map<const char*, std::function<std::optional<std::string>(std::string_view)>> COMMANDS{
{"?", Cmd$_questionMark},
{"g", Cmd$g},
{"G", Cmd$G},
{"m", Cmd$m},
{"M", Cmd$M},
{"z0,", Cmd$z1},
{"Z0,", Cmd$Z1},
{"z1,", Cmd$z1},
{"Z1,", Cmd$Z1},
{"vMustReplyEmpty", Cmd$vMustReplyEmpty},
{"qSupported", Cmd$qSupported},
};
bool IsPacketInterrupt(std::string_view data)
bool GDBServer::IsPacketInterrupt(std::string_view data)
{
return (data.size() >= 1) && (data[data.size() - 1] == '\003');
DebugAssert(data.size() >= 1);
return (data[data.size() - 1] == '\003');
}
bool IsPacketContinue(std::string_view data)
bool GDBServer::IsPacketContinue(std::string_view data)
{
return (data.size() >= 5) && (data.substr(data.size() - 5) == "$c#63");
}
bool IsPacketComplete(std::string_view data)
bool GDBServer::IsPacketComplete(std::string_view data)
{
return ((data.size() == 1) && (data[0] == '\003')) || ((data.size() > 3) && (*(data.end() - 3) == '#'));
}
std::string ProcessPacket(std::string_view data)
bool GDBServer::ProcessPacket(ClientSocket* client, std::string_view data)
{
std::string_view trimmedData = data;
// Eat ACKs.
while (!trimmedData.empty() && (trimmedData[0] == '+' || trimmedData[0] == '-'))
{
if (trimmedData[0] == '-')
{
ERROR_LOG("Received negative ack");
}
trimmedData = trimmedData.substr(1);
}
// Validate packet.
auto packet = DeserializePacket(trimmedData);
if (!packet)
if ((data.size() < 4) || (data[0] != '$') || (data[data.size() - 3] != '#'))
{
ERROR_LOG("Malformed packet '{}'", trimmedData);
return "-";
ERROR_LOG("Invalid packet: {}", data);
return false;
}
std::optional<std::string> reply = {""};
// Verify checksum.
const std::string_view request = data.substr(1, data.size() - 4);
const u8 packet_checksum = StringUtil::FromChars<u8>(data.substr(data.size() - 2, 2), 16).value_or(0);
const u8 computed_checksum = ComputeChecksum(request);
if (packet_checksum != computed_checksum)
{
ERROR_LOG("Incorrect checksum, expected 0x{:02x} got 0x{:02x} for '{}'", computed_checksum, packet_checksum, data);
return false;
}
// Try to invoke packet command.
bool processed = false;
for (const auto& command : COMMANDS)
{
if (packet->starts_with(command.first))
if (request.starts_with(command.first))
{
DEBUG_LOG("Processing command '{}'", command.first);
DEV_LOG("Processing command '{}'", command.first);
// Invoke command, remove command name from payload.
reply = command.second(packet->substr(strlen(command.first)));
processed = true;
break;
return command.second(client, request.substr(command.first.size()));
}
}
if (!processed)
WARNING_LOG("Failed to process packet '{}'", trimmedData);
return reply ? "+" + SerializePacket(*reply) : "+";
// Don't bail out on unknown command
WARNING_LOG("Failed to process packet '{}'", request);
client->SendReplyWithAck({});
return true;
}
} // namespace GDBProtocol
namespace GDBServer {
namespace {
class ClientSocket final : public BufferedStreamSocket
{
public:
ClientSocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor);
~ClientSocket() override;
void OnSystemPaused();
void OnSystemResumed();
protected:
void OnConnected() override;
void OnDisconnected(const Error& error) override;
void OnRead() override;
private:
void SendPacket(std::string_view sv);
bool m_seen_resume = false;
};
} // namespace
static std::shared_ptr<ListenSocket> s_gdb_listen_socket;
static std::vector<std::shared_ptr<ClientSocket>> s_gdb_clients;
} // namespace GDBServer
GDBServer::ClientSocket::ClientSocket(SocketMultiplexer& multiplexer, SocketDescriptor descriptor)
: BufferedStreamSocket(multiplexer, descriptor, 65536, 65536)
{
@ -433,25 +486,35 @@ void GDBServer::ClientSocket::OnRead()
const std::string_view current_packet(reinterpret_cast<const char*>(buffer.data() + buffer_offset),
current_packet_size);
if (GDBProtocol::IsPacketInterrupt(current_packet))
if (GDBServer::IsPacketAck(current_packet))
{
// Eat ACKs.
if (current_packet[0] == '-')
ERROR_LOG("Received negative ack");
packet_complete = true;
break;
}
else if (GDBServer::IsPacketInterrupt(current_packet))
{
DEV_LOG("{} > Interrupt request", GetRemoteAddress().ToString());
System::PauseSystem(true);
packet_complete = true;
break;
}
else if (GDBProtocol::IsPacketContinue(current_packet))
else if (GDBServer::IsPacketContinue(current_packet))
{
DEV_LOG("{} > Continue request", GetRemoteAddress().ToString());
System::PauseSystem(false);
packet_complete = true;
break;
}
else if (GDBProtocol::IsPacketComplete(current_packet))
else if (GDBServer::IsPacketComplete(current_packet))
{
// TODO: Make this not copy.
DEV_LOG("{} > {}", GetRemoteAddress().ToString(), current_packet);
SendPacket(GDBProtocol::ProcessPacket(current_packet));
DEBUG_LOG("{} > {}", GetRemoteAddress().ToString(), current_packet);
if (!ProcessPacket(this, current_packet))
SendPacket("-");
packet_complete = true;
break;
}
@ -459,7 +522,9 @@ void GDBServer::ClientSocket::OnRead()
if (!packet_complete)
{
WARNING_LOG("Incomplete packet, got {} bytes.", buffer.size() - buffer_offset);
WARNING_LOG(
"Incomplete packet, got {} bytes: {}", buffer.size() - buffer_offset,
std::string_view(reinterpret_cast<const char*>(buffer.data() + buffer_offset), buffer.size() - buffer_offset));
break;
}
else
@ -476,7 +541,7 @@ void GDBServer::ClientSocket::SendPacket(std::string_view sv)
if (sv.empty())
return;
WARNING_LOG("Write: {}", sv);
DEBUG_LOG("Send reply: {}", sv);
if (size_t written = Write(sv.data(), sv.length()); written != sv.length())
ERROR_LOG("Only wrote {} of {} bytes.", written, sv.length());
}
@ -489,7 +554,7 @@ void GDBServer::ClientSocket::OnSystemPaused()
m_seen_resume = false;
// Generate a stop reply packet, insert '?' command to generate it.
SendPacket(GDBProtocol::ProcessPacket("$?#3f"));
SendReplyWithAck("S00");
}
void GDBServer::ClientSocket::OnSystemResumed()
@ -500,6 +565,11 @@ void GDBServer::ClientSocket::OnSystemResumed()
SendPacket("+");
}
void GDBServer::ClientSocket::SendReplyWithAck(std::string_view reply)
{
SendPacket(SmallString::from_format("+${}#{:02x}", reply, ComputeChecksum(reply)));
}
bool GDBServer::Initialize(u16 port)
{
Error error;

View file

@ -531,6 +531,19 @@ void GPU::WriteRegister(u32 offset, u32 value)
if (m_gpu_dump) [[unlikely]]
m_gpu_dump->WriteGP0Packet(value);
// FIFO can be overflowed through direct GP0 writes if the command tick event hasn't run, because
// there's no backpressure applied to the CPU. Instead force the GPU to run and catch up.
if (m_fifo.GetSize() >= m_fifo_size) [[unlikely]]
{
s_command_tick_event.InvokeEarly();
if (m_fifo.GetSize() >= m_fifo.GetCapacity()) [[unlikely]]
{
WARNING_LOG("GPU FIFO overflow via GP0 write, size={}", m_fifo.GetSize());
return;
}
}
m_fifo.Push(value);
ExecuteCommands();
return;
@ -653,7 +666,7 @@ float GPU::ComputeDisplayAspectRatio() const
if (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow)
{
const WindowInfo& wi = GPUThread::GetRenderWindowInfo();
if (!wi.IsSurfaceless())
if (!wi.IsSurfaceless() && wi.surface_width > 0 && wi.surface_height > 0)
ar = static_cast<float>(wi.surface_width) / static_cast<float>(wi.surface_height);
}
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Custom)
@ -1942,7 +1955,7 @@ void GPU::UpdateDisplay(bool submit_frame)
cmd->display_origin_left = m_crtc_state.display_origin_left;
cmd->display_origin_top = m_crtc_state.display_origin_top;
cmd->display_vram_left = m_crtc_state.display_vram_left;
cmd->display_vram_top = m_crtc_state.display_vram_top + (interlaced_field & BoolToUInt8(line_skip));
cmd->display_vram_top = m_crtc_state.display_vram_top;
cmd->display_vram_width = m_crtc_state.display_vram_width;
cmd->display_vram_height = m_crtc_state.display_vram_height >> BoolToUInt8(interlaced);
cmd->X = m_crtc_state.regs.X;

View file

@ -31,26 +31,6 @@ LOG_CHANNEL(GPU);
namespace {
struct Counters
{
u32 num_reads;
u32 num_writes;
u32 num_copies;
u32 num_vertices;
u32 num_primitives;
};
struct Stats : Counters
{
size_t host_buffer_streamed;
u32 host_num_draws;
u32 host_num_barriers;
u32 host_num_render_passes;
u32 host_num_copies;
u32 host_num_downloads;
u32 host_num_uploads;
};
struct ALIGN_TO_CACHE_LINE CPUThreadState
{
static constexpr u32 WAIT_NONE = 0;
@ -65,8 +45,8 @@ struct ALIGN_TO_CACHE_LINE CPUThreadState
} // namespace
static Counters s_counters = {};
static Stats s_stats = {};
GPUBackend::Counters GPUBackend::s_counters = {};
GPUBackend::Stats GPUBackend::s_stats = {};
static CPUThreadState s_cpu_thread_state = {};
@ -603,10 +583,21 @@ void GPUBackend::GetStatsString(SmallStringBase& str) const
{
if (IsUsingHardwareBackend())
{
str.format("{}{} HW | {} P | {} DC | {} B | {} RP | {} RB | {} C | {} W",
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
s_stats.num_primitives, s_stats.host_num_draws, s_stats.host_num_barriers,
s_stats.host_num_render_passes, s_stats.host_num_downloads, s_stats.num_copies, s_stats.num_writes);
if (g_gpu_settings.gpu_pgxp_depth_buffer)
{
str.format("{}{} HW | {} P | {} DC | {} B | {} RP | {} RB | {} C | {} W | {} DBC",
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
s_stats.num_primitives, s_stats.host_num_draws, s_stats.host_num_barriers,
s_stats.host_num_render_passes, s_stats.host_num_downloads, s_stats.num_copies, s_stats.num_writes,
s_stats.num_depth_buffer_clears);
}
else
{
str.format("{}{} HW | {} P | {} DC | {} B | {} RP | {} RB | {} C | {} W",
GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()), g_gpu_settings.gpu_use_thread ? "-MT" : "",
s_stats.num_primitives, s_stats.host_num_draws, s_stats.host_num_barriers,
s_stats.host_num_render_passes, s_stats.host_num_downloads, s_stats.num_copies, s_stats.num_writes);
}
}
else
{
@ -644,6 +635,7 @@ void GPUBackend::UpdateStatistics(u32 frame_count)
UPDATE_COUNTER(num_copies);
UPDATE_COUNTER(num_vertices);
UPDATE_COUNTER(num_primitives);
UPDATE_COUNTER(num_depth_buffer_clears);
// UPDATE_COUNTER(num_read_texture_updates);
// UPDATE_COUNTER(num_ubo_updates);

View file

@ -126,6 +126,27 @@ protected:
DEINTERLACE_BUFFER_COUNT = 4,
};
struct Counters
{
u32 num_reads;
u32 num_writes;
u32 num_copies;
u32 num_vertices;
u32 num_primitives;
u32 num_depth_buffer_clears;
};
struct Stats : Counters
{
size_t host_buffer_streamed;
u32 host_num_draws;
u32 host_num_barriers;
u32 host_num_render_passes;
u32 host_num_copies;
u32 host_num_downloads;
u32 host_num_uploads;
};
virtual void ReadVRAM(u32 x, u32 y, u32 width, u32 height) = 0;
virtual void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering,
u8 interlaced_display_field) = 0;
@ -157,6 +178,9 @@ protected:
GPUPresenter& m_presenter;
GSVector4i m_clamped_drawing_area = {};
static Counters s_counters;
static Stats s_stats;
private:
static void ReleaseQueuedFrame();
};

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gpu_hw.h"
@ -8,6 +8,7 @@
#include "gpu_hw_shadergen.h"
#include "gpu_presenter.h"
#include "gpu_sw_rasterizer.h"
#include "gte_types.h"
#include "host.h"
#include "imgui_overlays.h"
#include "settings.h"
@ -153,6 +154,21 @@ ALWAYS_INLINE_RELEASE static GSVector4i GetVRAMTransferBounds(u32 x, u32 y, u32
return ret;
}
/// Returns true if the below function should be applied.
ALWAYS_INLINE static bool ShouldTruncate32To16(const GPUBackendDrawCommand* cmd)
{
return (!cmd->texture_enable && !cmd->shading_enable && !cmd->dither_enable &&
g_gpu_settings.gpu_dithering_mode == GPUDitheringMode::TrueColor);
}
/// Truncates a 32-bit colour to 16-bit.
ALWAYS_INLINE static u32 Truncate32To16(u32 color)
{
return GSVector4i((GSVector4(GSVector4i::zext32(color).u8to32().srl32<3>()) / GSVector4::cxpr(31.0f)) *
GSVector4::cxpr(255.0f))
.rgba32();
}
namespace {
class ShaderCompileProgressTracker
{
@ -260,7 +276,7 @@ bool GPU_HW::Initialize(bool upload_vram, Error* error)
m_wireframe_mode = g_gpu_settings.gpu_wireframe_mode;
m_supports_dual_source_blend = features.dual_source_blend;
m_supports_framebuffer_fetch = features.framebuffer_fetch;
m_true_color = g_gpu_settings.gpu_true_color;
m_true_color = g_gpu_settings.IsUsingTrueColor();
m_pgxp_depth_buffer = g_gpu_settings.UsingPGXPDepthBuffer();
m_clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering);
m_compute_uv_range = m_clamp_uvs;
@ -445,21 +461,21 @@ bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error)
const u8 resolution_scale = Truncate8(CalculateResolutionScale());
const u8 multisamples = Truncate8(std::min<u32>(g_gpu_settings.gpu_multisamples, g_gpu_device->GetMaxMultisamples()));
const bool clamp_uvs = ShouldClampUVs(m_texture_filtering) || ShouldClampUVs(m_sprite_texture_filtering);
const bool framebuffer_changed =
(m_resolution_scale != resolution_scale || m_multisamples != multisamples ||
g_gpu_settings.IsUsingAccurateBlending() != old_settings.IsUsingAccurateBlending() ||
m_pgxp_depth_buffer != g_gpu_settings.UsingPGXPDepthBuffer() ||
(!old_settings.gpu_texture_cache && g_gpu_settings.gpu_texture_cache));
const bool framebuffer_changed = (m_resolution_scale != resolution_scale || m_multisamples != multisamples ||
g_gpu_settings.IsUsingShaderBlending() != old_settings.IsUsingShaderBlending() ||
m_pgxp_depth_buffer != g_gpu_settings.UsingPGXPDepthBuffer() ||
(!old_settings.gpu_texture_cache && g_gpu_settings.gpu_texture_cache));
const bool shaders_changed =
((m_resolution_scale > 1) != (resolution_scale > 1) || m_multisamples != multisamples ||
m_true_color != g_gpu_settings.gpu_true_color ||
m_true_color != g_gpu_settings.IsUsingTrueColor() ||
(old_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive) !=
(g_gpu_settings.display_deinterlacing_mode == DisplayDeinterlacingMode::Progressive) ||
(multisamples > 1 && g_gpu_settings.gpu_per_sample_shading != old_settings.gpu_per_sample_shading) ||
(resolution_scale > 1 && g_gpu_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering) ||
(resolution_scale > 1 && (g_gpu_settings.IsUsingScaledDithering() != old_settings.IsUsingScaledDithering() ||
g_gpu_settings.gpu_scaled_interlacing != old_settings.gpu_scaled_interlacing)) ||
(resolution_scale > 1 && g_gpu_settings.gpu_texture_filter == GPUTextureFilter::Nearest &&
g_gpu_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords) ||
g_gpu_settings.IsUsingAccurateBlending() != old_settings.IsUsingAccurateBlending() ||
g_gpu_settings.IsUsingShaderBlending() != old_settings.IsUsingShaderBlending() ||
m_texture_filtering != g_gpu_settings.gpu_texture_filter ||
m_sprite_texture_filtering != g_gpu_settings.gpu_sprite_texture_filter || m_clamp_uvs != clamp_uvs ||
(features.geometry_shaders && g_gpu_settings.gpu_wireframe_mode != old_settings.gpu_wireframe_mode) ||
@ -474,7 +490,7 @@ bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error)
const bool downsampling_shaders_changed =
((m_resolution_scale > 1) != (resolution_scale > 1) ||
(resolution_scale > 1 && (g_gpu_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
(m_downsample_mode == GPUDownsampleMode::Box &&
(g_gpu_settings.gpu_downsample_mode == GPUDownsampleMode::Box &&
(resolution_scale != m_resolution_scale ||
g_gpu_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale)))));
@ -520,7 +536,7 @@ bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error)
m_line_detect_mode = (m_resolution_scale > 1) ? g_gpu_settings.gpu_line_detect_mode : GPULineDetectMode::Disabled;
m_downsample_mode = GetDownsampleMode(resolution_scale);
m_wireframe_mode = g_gpu_settings.gpu_wireframe_mode;
m_true_color = g_gpu_settings.gpu_true_color;
m_true_color = g_gpu_settings.IsUsingTrueColor();
m_clamp_uvs = clamp_uvs;
m_compute_uv_range = m_clamp_uvs;
m_allow_sprite_mode = ShouldAllowSpriteMode(resolution_scale, m_texture_filtering, m_sprite_texture_filtering);
@ -580,7 +596,7 @@ bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error)
else if (m_vram_depth_texture && depth_buffer_changed)
{
if (m_pgxp_depth_buffer)
ClearDepthBuffer();
ClearDepthBuffer(false);
else if (m_write_mask_as_depth)
UpdateDepthBufferFromMaskBit();
}
@ -658,23 +674,23 @@ void GPU_HW::CheckSettings()
m_allow_sprite_mode = ShouldAllowSpriteMode(m_resolution_scale, m_texture_filtering, m_sprite_texture_filtering);
}
if (g_gpu_settings.IsUsingAccurateBlending() && !m_supports_framebuffer_fetch && !features.feedback_loops &&
if (g_gpu_settings.IsUsingShaderBlending() && !m_supports_framebuffer_fetch && !features.feedback_loops &&
!features.raster_order_views)
{
// m_allow_shader_blend/m_prefer_shader_blend will be cleared in pipeline compile.
Host::AddIconOSDMessage(
"AccurateBlendingUnsupported", ICON_EMOJI_WARNING,
TRANSLATE_STR("GPU_HW", "Accurate blending is not supported by your current GPU.\nIt requires framebuffer fetch, "
TRANSLATE_STR("GPU_HW", "Shader blending is not supported by your current GPU.\nIt requires framebuffer fetch, "
"feedback loops, or rasterizer order views."),
Host::OSD_WARNING_DURATION);
}
else if (IsUsingMultisampling() && !features.framebuffer_fetch &&
((g_gpu_settings.IsUsingAccurateBlending() && features.raster_order_views) ||
((g_gpu_settings.IsUsingShaderBlending() && features.raster_order_views) ||
(m_pgxp_depth_buffer && features.raster_order_views && !features.feedback_loops)))
{
Host::AddIconOSDMessage(
"AccurateBlendingUnsupported", ICON_EMOJI_WARNING,
TRANSLATE_STR("GPU_HW", "Multisample anti-aliasing is not supported when using ROV blending."),
TRANSLATE_STR("GPU_HW", "Multisample anti-aliasing is not supported when using shader blending."),
Host::OSD_WARNING_DURATION);
m_multisamples = 1;
}
@ -705,7 +721,19 @@ void GPU_HW::CheckSettings()
{
const u32 resolution_scale = CalculateResolutionScale();
const u32 box_downscale = GetBoxDownsampleScale(resolution_scale);
if (box_downscale != g_gpu_settings.gpu_downsample_scale || box_downscale == resolution_scale)
if (box_downscale == resolution_scale)
{
m_downsample_mode = GPUDownsampleMode::Disabled;
Host::AddIconOSDMessage(
"BoxDownsampleUnsupported", ICON_FA_PAINT_BRUSH,
fmt::format(
TRANSLATE_FS("GPU_HW",
"Resolution scale {0}x is not divisible by downsample scale {1}x, downsampling disabled."),
resolution_scale, g_gpu_settings.gpu_downsample_scale),
Host::OSD_WARNING_DURATION);
}
else if (box_downscale != g_gpu_settings.gpu_downsample_scale)
{
Host::AddIconOSDMessage(
"BoxDownsampleUnsupported", ICON_FA_PAINT_BRUSH,
@ -718,9 +746,6 @@ void GPU_HW::CheckSettings()
{
Host::RemoveKeyedOSDMessage("BoxDownsampleUnsupported");
}
if (box_downscale == g_gpu_settings.gpu_resolution_scale)
m_downsample_mode = GPUDownsampleMode::Disabled;
}
}
@ -835,8 +860,10 @@ void GPU_HW::PrintSettingsToLog()
(g_gpu_settings.gpu_per_sample_shading && g_gpu_device->GetFeatures().per_sample_shading) ?
" (per sample shading)" :
"");
INFO_LOG("Dithering: {}", m_true_color ? "Disabled" : "Enabled",
(!m_true_color && g_gpu_settings.gpu_scaled_dithering));
INFO_LOG("Dithering: {}", Settings::GetGPUDitheringModeDisplayName(g_gpu_settings.gpu_dithering_mode));
INFO_LOG("Deinterlacing: {}{}",
Settings::GetDisplayDeinterlacingModeDisplayName(g_gpu_settings.display_deinterlacing_mode),
(m_resolution_scale > 1 && g_gpu_settings.gpu_scaled_interlacing) ? " (scaled)" : "");
INFO_LOG("Force round texture coordinates: {}",
(m_resolution_scale > 1 && g_gpu_settings.gpu_force_round_texcoords) ? "Enabled" : "Disabled");
INFO_LOG("Texture Filtering: {}/{}", Settings::GetTextureFilterDisplayName(m_texture_filtering),
@ -1040,8 +1067,9 @@ bool GPU_HW::CompilePipelines(Error* error)
const bool per_sample_shading = (msaa && g_gpu_settings.gpu_per_sample_shading && features.per_sample_shading);
const bool force_round_texcoords =
(upscaled && m_texture_filtering == GPUTextureFilter::Nearest && g_gpu_settings.gpu_force_round_texcoords);
const bool true_color = g_gpu_settings.gpu_true_color;
const bool scaled_dithering = (!m_true_color && upscaled && g_gpu_settings.gpu_scaled_dithering);
const bool true_color = g_gpu_settings.IsUsingTrueColor();
const bool scaled_dithering = (!m_true_color && upscaled && g_gpu_settings.IsUsingScaledDithering());
const bool scaled_interlacing = (upscaled && g_gpu_settings.gpu_scaled_interlacing);
const bool disable_color_perspective = (features.noperspective_interpolation && ShouldDisableColorPerspective());
const bool needs_page_texture = m_use_texture_cache;
const bool force_progressive_scan =
@ -1054,10 +1082,10 @@ bool GPU_HW::CompilePipelines(Error* error)
// Abuse the depth buffer for the mask bit when it's free (FBFetch), or PGXP depth buffering is enabled.
m_allow_shader_blend = features.framebuffer_fetch ||
((features.feedback_loops || features.raster_order_views) &&
(m_pgxp_depth_buffer || g_gpu_settings.IsUsingAccurateBlending() ||
(m_pgxp_depth_buffer || g_gpu_settings.IsUsingShaderBlending() ||
(!m_supports_dual_source_blend && (IsBlendedTextureFiltering(m_texture_filtering) ||
IsBlendedTextureFiltering(m_sprite_texture_filtering)))));
m_prefer_shader_blend = (m_allow_shader_blend && g_gpu_settings.IsUsingAccurateBlending());
m_prefer_shader_blend = (m_allow_shader_blend && g_gpu_settings.IsUsingShaderBlending());
m_use_rov_for_shader_blend = (m_allow_shader_blend && !features.framebuffer_fetch && features.raster_order_views &&
(m_prefer_shader_blend || !features.feedback_loops));
m_write_mask_as_depth = (!m_pgxp_depth_buffer && !features.framebuffer_fetch && !m_prefer_shader_blend);
@ -1121,6 +1149,7 @@ bool GPU_HW::CompilePipelines(Error* error)
p.reset();
m_vram_update_depth_pipeline.reset();
m_vram_write_replacement_pipeline.reset();
m_clear_depth_pipeline.reset();
m_copy_depth_pipeline.reset();
ShaderCompileProgressTracker progress("Compiling Pipelines", total_items);
@ -1247,8 +1276,8 @@ bool GPU_HW::CompilePipelines(Error* error)
shader_texmode, sprite ? m_sprite_texture_filtering : m_texture_filtering, upscaled, msaa,
per_sample_shading, uv_limits, !sprite && force_round_texcoords, true_color,
ConvertToBoolUnchecked(dithering), scaled_dithering, disable_color_perspective,
ConvertToBoolUnchecked(interlacing), ConvertToBoolUnchecked(check_mask), m_write_mask_as_depth,
use_rov, needs_rov_depth, rov_depth_test, rov_depth_write);
ConvertToBoolUnchecked(interlacing), scaled_interlacing, ConvertToBoolUnchecked(check_mask),
m_write_mask_as_depth, use_rov, needs_rov_depth, rov_depth_test, rov_depth_write);
if (!(batch_fragment_shaders[depth_test][render_mode][transparency_mode][texture_mode][check_mask]
[dithering][interlacing] = g_gpu_device->CreateShader(
@ -1667,6 +1696,30 @@ bool GPU_HW::CompilePipelines(Error* error)
plconfig.SetTargetFormats(VRAM_DS_COLOR_FORMAT);
if (!(m_copy_depth_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
return false;
fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateVRAMClearDepthFragmentShader(m_use_rov_for_shader_blend), error);
if (!fs)
return false;
SetScreenQuadInputLayout(plconfig);
plconfig.vertex_shader = m_screen_quad_vertex_shader.get();
plconfig.fragment_shader = fs.get();
if (!m_use_rov_for_shader_blend)
{
plconfig.SetTargetFormats(VRAM_RT_FORMAT, depth_buffer_format);
plconfig.render_pass_flags =
needs_feedback_loop ? GPUPipeline::ColorFeedbackLoop : GPUPipeline::NoRenderPassFlags;
plconfig.blend.write_mask = 0;
plconfig.depth = GPUPipeline::DepthState::GetAlwaysWriteState();
}
else
{
plconfig.SetTargetFormats(depth_buffer_format);
}
if (!(m_clear_depth_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
return false;
}
if (!CompileResolutionDependentPipelines(error) || !CompileDownsamplePipelines(error))
@ -1765,6 +1818,9 @@ bool GPU_HW::CompileDownsamplePipelines(Error* error)
m_downsample_lod_sampler.reset();
m_downsample_composite_sampler.reset();
if (m_downsample_mode == GPUDownsampleMode::Disabled)
return true;
const GPU_HW_ShaderGen shadergen(g_gpu_device->GetRenderAPI(), m_supports_dual_source_blend,
m_supports_framebuffer_fetch);
@ -1949,7 +2005,7 @@ void GPU_HW::UpdateDepthBufferFromMaskBit()
SetScissor();
}
void GPU_HW::CopyAndClearDepthBuffer()
void GPU_HW::CopyAndClearDepthBuffer(bool only_drawing_area)
{
if (!m_depth_was_copied)
{
@ -1976,18 +2032,41 @@ void GPU_HW::CopyAndClearDepthBuffer()
m_depth_was_copied = true;
}
ClearDepthBuffer();
ClearDepthBuffer(only_drawing_area);
}
void GPU_HW::ClearDepthBuffer()
void GPU_HW::ClearDepthBuffer(bool only_drawing_area)
{
GL_SCOPE("GPU_HW::ClearDepthBuffer()");
GL_SCOPE_FMT("GPU_HW::ClearDepthBuffer({})", only_drawing_area ? "Only Drawing Area" : "Full Buffer");
DebugAssert(m_pgxp_depth_buffer);
if (m_use_rov_for_shader_blend)
g_gpu_device->ClearRenderTarget(m_vram_depth_texture.get(), 0xFF);
if (only_drawing_area)
{
g_gpu_device->SetPipeline(m_clear_depth_pipeline.get());
const GSVector4i clear_bounds = m_clamped_drawing_area.mul32l(GSVector4i(m_resolution_scale));
// need to re-bind for rov, because we can't turn colour writes off for only the first target
if (!m_use_rov_for_shader_blend)
{
DrawScreenQuad(clear_bounds, m_vram_depth_texture->GetSizeVec());
}
else
{
g_gpu_device->SetRenderTarget(m_vram_depth_texture.get());
DrawScreenQuad(clear_bounds, m_vram_depth_texture->GetSizeVec());
SetVRAMRenderTarget();
}
}
else
g_gpu_device->ClearDepth(m_vram_depth_texture.get(), 1.0f);
{
if (m_use_rov_for_shader_blend)
g_gpu_device->ClearRenderTarget(m_vram_depth_texture.get(), 0xFF);
else
g_gpu_device->ClearDepth(m_vram_depth_texture.get(), 1.0f);
}
m_last_depth_z = 1.0f;
s_counters.num_depth_buffer_clears++;
}
void GPU_HW::SetScissor()
@ -2404,8 +2483,12 @@ void GPU_HW::CheckForDepthClear(const GPUBackendDrawCommand* cmd, const BatchVer
if ((average_z - m_last_depth_z) >= g_gpu_settings.gpu_pgxp_depth_clear_threshold)
{
GL_INS_FMT("Clear depth buffer avg={} last={} threshold={}", average_z * static_cast<float>(GTE::MAX_Z),
m_last_depth_z * static_cast<float>(GTE::MAX_Z),
g_gpu_settings.gpu_pgxp_depth_clear_threshold * static_cast<float>(GTE::MAX_Z));
FlushRender();
CopyAndClearDepthBuffer();
CopyAndClearDepthBuffer(true);
EnsureVertexBufferSpaceForCommand(cmd);
}
@ -2457,7 +2540,7 @@ void GPU_HW::DrawLine(const GPUBackendDrawLineCommand* cmd)
}
AddDrawnRectangle(clamped_rect);
DrawLine(GSVector4(bounds), start_color, end_color, depth);
DrawLine(cmd, GSVector4(bounds), start_color, end_color, depth);
}
if (ShouldDrawWithSoftwareRenderer())
@ -2500,7 +2583,7 @@ void GPU_HW::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
}
AddDrawnRectangle(clamped_rect);
DrawLine(bounds, start_color, end_color, depth);
DrawLine(cmd, bounds, start_color, end_color, depth);
}
if (ShouldDrawWithSoftwareRenderer())
@ -2522,10 +2605,16 @@ void GPU_HW::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd)
}
}
void GPU_HW::DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth)
void GPU_HW::DrawLine(const GPUBackendDrawCommand* cmd, const GSVector4 bounds, u32 col0, u32 col1, float depth)
{
DebugAssert(m_batch_vertex_space >= 4 && m_batch_index_space >= 6);
if (ShouldTruncate32To16(cmd))
{
col0 = Truncate32To16(col0);
col1 = Truncate32To16(col1);
}
const float x0 = bounds.x;
const float y0 = bounds.y;
const float x1 = bounds.z;
@ -2649,7 +2738,9 @@ void GPU_HW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
const s32 pos_x = cmd->x;
const s32 pos_y = cmd->y;
const u32 texpage = m_draw_mode.bits;
const u32 color = (cmd->texture_enable && cmd->raw_texture_enable) ? UINT32_C(0x00808080) : cmd->color;
const u32 color = (cmd->texture_enable && cmd->raw_texture_enable) ?
UINT32_C(0x00808080) :
(ShouldTruncate32To16(cmd) ? Truncate32To16(cmd->color) : cmd->color);
const float depth = GetCurrentNormalizedVertexDepth();
const u32 orig_tex_left = ZeroExtend32(Truncate8(cmd->texcoord));
const u32 orig_tex_top = ZeroExtend32(cmd->texcoord) >> 8;
@ -2912,6 +3003,12 @@ ALWAYS_INLINE_RELEASE bool GPU_HW::BeginPolygonDraw(const GPUBackendDrawCommand*
}
}
if (ShouldTruncate32To16(cmd))
{
for (u32 i = 0; i < 4; i++)
vertices[i].color = Truncate32To16(vertices[i].color);
}
PrepareDraw(cmd);
return true;
}
@ -3722,7 +3819,7 @@ void GPU_HW::PrepareDraw(const GPUBackendDrawCommand* cmd)
if (m_pgxp_depth_buffer && m_last_depth_z < 1.0f)
{
FlushRender();
CopyAndClearDepthBuffer();
CopyAndClearDepthBuffer(false);
EnsureVertexBufferSpaceForCommand(cmd);
}
}
@ -3834,7 +3931,9 @@ void GPU_HW::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
const u32 line_skip = BoolToUInt32(cmd->interlaced_display_interleaved);
const u32 resolution_scale = cmd->display_24bit ? 1 : m_resolution_scale;
const u32 scaled_vram_offset_x = cmd->display_vram_left * resolution_scale;
const u32 scaled_vram_offset_y = cmd->display_vram_top * resolution_scale;
const u32 scaled_vram_offset_y =
cmd->display_vram_top * resolution_scale +
(BoolToUInt8(cmd->interlaced_display_field) & BoolToUInt8(cmd->interlaced_display_interleaved));
const u32 scaled_display_width = cmd->display_vram_width * resolution_scale;
const u32 scaled_display_height = cmd->display_vram_height * resolution_scale;
bool drew_anything = false;

View file

@ -186,8 +186,8 @@ private:
void UpdateVRAMReadTexture(bool drawn, bool written);
void UpdateDepthBufferFromMaskBit();
void CopyAndClearDepthBuffer();
void ClearDepthBuffer();
void CopyAndClearDepthBuffer(bool only_drawing_area);
void ClearDepthBuffer(bool only_drawing_area);
void SetScissor();
void SetVRAMRenderTarget();
void DeactivateROV();
@ -242,7 +242,7 @@ private:
bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
/// Expands a line into two triangles.
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
void DrawLine(const GPUBackendDrawCommand* cmd, const GSVector4 bounds, u32 col0, u32 col1, float depth);
/// Computes partial derivatives and area for the given triangle. Needed for sprite/line detection.
static void ComputeUVPartialDerivatives(const BatchVertex* vertices, float* dudx, float* dudy, float* dvdx,
@ -365,6 +365,7 @@ private:
std::unique_ptr<GPUTexture> m_vram_extract_texture;
std::unique_ptr<GPUTexture> m_vram_extract_depth_texture;
std::unique_ptr<GPUPipeline> m_copy_depth_pipeline;
std::unique_ptr<GPUPipeline> m_clear_depth_pipeline;
std::unique_ptr<PostProcessing::Chain> m_internal_postfx;
std::unique_ptr<GPUTexture> m_downsample_texture;

View file

@ -732,8 +732,8 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
GPU_HW::BatchRenderMode render_mode, GPUTransparencyMode transparency, GPU_HW::BatchTextureMode texture_mode,
GPUTextureFilter texture_filtering, bool upscaled, bool msaa, bool per_sample_shading, bool uv_limits,
bool force_round_texcoords, bool true_color, bool dithering, bool scaled_dithering, bool disable_color_perspective,
bool interlacing, bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth, bool rov_depth_test,
bool rov_depth_write) const
bool interlacing, bool scaled_interlacing, bool check_mask, bool write_mask_as_depth, bool use_rov,
bool use_rov_depth, bool rov_depth_test, bool rov_depth_write) const
{
DebugAssert(!true_color || !dithering); // Should not be doing dithering+true color.
@ -766,6 +766,7 @@ std::string GPU_HW_ShaderGen::GenerateBatchFragmentShader(
DefineMacro(ss, "DITHERING", dithering);
DefineMacro(ss, "DITHERING_SCALED", dithering && scaled_dithering);
DefineMacro(ss, "INTERLACING", interlacing);
DefineMacro(ss, "INTERLACING_SCALED", interlacing && scaled_interlacing);
DefineMacro(ss, "TRUE_COLOR", true_color);
DefineMacro(ss, "TEXTURE_FILTERING", texture_filtering != GPUTextureFilter::Nearest);
DefineMacro(ss, "UV_LIMITS", uv_limits);
@ -992,8 +993,13 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
float oalpha;
#if INTERLACING
if ((fragpos.y & 1u) == u_interlaced_displayed_field)
discard;
#if INTERLACING_SCALED || !UPSCALED
if ((fragpos.y & 1u) == u_interlaced_displayed_field)
discard;
#else
if ((uint(v_pos.y * u_rcp_resolution_scale) & 1u) == u_interlaced_displayed_field)
discard;
#endif
#endif
#if TEXTURED
@ -1694,6 +1700,24 @@ std::string GPU_HW_ShaderGen::GenerateVRAMUpdateDepthFragmentShader(bool msaa) c
return std::move(ss).str();
}
std::string GPU_HW_ShaderGen::GenerateVRAMClearDepthFragmentShader(bool write_depth_as_rt) const
{
std::stringstream ss;
WriteHeader(ss);
DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
DeclareFragmentEntryPoint(ss, 0, 1, {}, false, BoolToUInt32(write_depth_as_rt), false, false, false, false, false);
ss << R"(
{
#if WRITE_DEPTH_AS_RT
o_col0 = float4(1.0f, 0.0f, 0.0f, 0.0f);
#endif
}
)";
return std::move(ss).str();
}
void GPU_HW_ShaderGen::WriteAdaptiveDownsampleUniformBuffer(std::stringstream& ss) const
{
DeclareUniformBuffer(ss, {"float2 u_uv_min", "float2 u_uv_max", "float2 u_pixel_size", "float u_lod"}, true);

View file

@ -23,8 +23,9 @@ public:
bool upscaled, bool msaa, bool per_sample_shading, bool uv_limits,
bool force_round_texcoords, bool true_color, bool dithering,
bool scaled_dithering, bool disable_color_perspective, bool interlacing,
bool check_mask, bool write_mask_as_depth, bool use_rov, bool use_rov_depth,
bool rov_depth_test, bool rov_depth_write) const;
bool scaled_interlacing, bool check_mask, bool write_mask_as_depth,
bool use_rov, bool use_rov_depth, bool rov_depth_test,
bool rov_depth_write) const;
std::string GenerateWireframeGeometryShader() const;
std::string GenerateWireframeFragmentShader() const;
std::string GenerateVRAMReadFragmentShader(u32 resolution_scale, u32 multisamples) const;
@ -34,6 +35,7 @@ public:
std::string GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced, bool write_mask_as_depth,
bool write_depth_as_rt) const;
std::string GenerateVRAMUpdateDepthFragmentShader(bool msaa) const;
std::string GenerateVRAMClearDepthFragmentShader(bool write_depth_as_rt) const;
std::string GenerateVRAMExtractFragmentShader(u32 resolution_scale, u32 multisamples, bool color_24bit,
bool depth_buffer) const;
std::string GenerateVRAMReplacementBlitFragmentShader() const;

View file

@ -2809,7 +2809,7 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32
SmallString filename = name.ToString();
filename.append(".png");
const std::string path = Path::Combine(dump_directory, filename);
std::string path = Path::Combine(dump_directory, filename);
if (FileSystem::FileExists(path.c_str()))
return;
@ -2819,39 +2819,41 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32
GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, image.GetPixels(),
image.GetPitch(), width, height, GPUTexture::Format::RGBA8);
// TODO: Vectorize this.
u32* image_pixels = reinterpret_cast<u32*>(image.GetPixels());
const u32* image_pixels_end = image_pixels + (width * height);
if (s_state.config.dump_texture_force_alpha_channel)
{
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
*pixel |= 0xFF000000u;
}
else
{
if (semitransparent)
System::QueueAsyncTask([path = std::move(path), image = std::move(image), width, height, semitransparent]() mutable {
// TODO: Vectorize this.
u32* image_pixels = reinterpret_cast<u32*>(image.GetPixels());
const u32* image_pixels_end = image_pixels + (width * height);
if (s_state.config.dump_texture_force_alpha_channel)
{
// Alpha channel should be inverted, because 0 means opaque, 1 is semitransparent.
// Pixel value of 0000 is still completely transparent.
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
{
const u32 val = *pixel;
*pixel = (val == 0u) ? 0u : ((val & 0xFFFFFFFu) | ((val & 0x80000000u) ? 0x80000000u : 0xFF000000u));
}
*pixel |= 0xFF000000u;
}
else
{
// Only cut out 0000 pixels.
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
if (semitransparent)
{
const u32 val = *pixel;
*pixel = (val == 0u) ? 0u : (val | 0xFF000000u);
// Alpha channel should be inverted, because 0 means opaque, 1 is semitransparent.
// Pixel value of 0000 is still completely transparent.
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
{
const u32 val = *pixel;
*pixel = (val == 0u) ? 0u : ((val & 0xFFFFFFFu) | ((val & 0x80000000u) ? 0x80000000u : 0xFF000000u));
}
}
else
{
// Only cut out 0000 pixels.
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
{
const u32 val = *pixel;
*pixel = (val == 0u) ? 0u : (val | 0xFF000000u);
}
}
}
}
if (!image.SaveToFile(path.c_str()))
ERROR_LOG("Failed to write texture dump to {}.", Path::GetFileName(path));
if (!image.SaveToFile(path.c_str()))
ERROR_LOG("Failed to write texture dump to {}.", Path::GetFileName(path));
});
}
bool GPUTextureCache::IsMatchingReplacementPalette(HashType full_palette_hash, GPUTextureMode mode,

View file

@ -115,7 +115,7 @@ bool GPUPresenter::CompileDisplayPipelines(bool display, bool deinterlace, bool
plconfig.SetTargetFormats(m_present_format);
std::unique_ptr<GPUShader> vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
shadergen.GenerateDisplayVertexShader(), error);
shadergen.GeneratePassthroughVertexShader(), error);
if (!vso)
return false;
GL_OBJECT_NAME(vso, "Display Vertex Shader");
@ -1047,19 +1047,39 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo
ImGuiManager::RenderSoftwareCursors();
ImGuiManager::RenderDebugWindows();
// render offscreen for transitions
if (FullscreenUI::IsTransitionActive())
{
GPUTexture* const rtex = FullscreenUI::GetTransitionRenderTexture(g_gpu_device->GetMainSwapChain());
if (rtex)
{
if (presenter)
presenter->RenderDisplay(rtex, rtex->GetSizeVec(), true, true);
else
g_gpu_device->ClearRenderTarget(rtex, GPUDevice::DEFAULT_CLEAR_COLOR);
g_gpu_device->SetRenderTarget(rtex);
g_gpu_device->RenderImGui(rtex);
}
}
}
GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain();
const GPUDevice::PresentResult pres =
skip_present ? GPUDevice::PresentResult::SkipPresent :
(presenter ? presenter->RenderDisplay(nullptr, swap_chain->GetSizeVec(), true, true) :
g_gpu_device->BeginPresent(swap_chain));
const GPUDevice::PresentResult pres = skip_present ?
GPUDevice::PresentResult::SkipPresent :
((presenter && !FullscreenUI::IsTransitionActive()) ?
presenter->RenderDisplay(nullptr, swap_chain->GetSizeVec(), true, true) :
g_gpu_device->BeginPresent(swap_chain));
if (pres == GPUDevice::PresentResult::OK)
{
if (presenter)
presenter->m_skipped_present_count = 0;
g_gpu_device->RenderImGui(swap_chain);
if (FullscreenUI::IsTransitionActive())
FullscreenUI::RenderTransitionBlend(swap_chain);
else
g_gpu_device->RenderImGui(swap_chain);
const GPUDevice::Features features = g_gpu_device->GetFeatures();
const bool scheduled_present = (present_time != 0);

View file

@ -89,6 +89,11 @@ public:
/// Reloads post-processing settings. Only callable from the CPU thread.
static void ReloadPostProcessingSettings(bool display, bool internal, bool reload_shaders);
// Draws the specified bounding box with display rotation and pre-rotation.
static void DrawScreenQuad(const GSVector4i rect, const GSVector4 uv_rect, const GSVector2i target_size,
const GSVector2i final_target_size, DisplayRotation uv_rotation,
WindowInfo::PreRotation prerotation);
private:
enum : u32
{
@ -109,9 +114,6 @@ private:
bool dst_alpha_blend, DisplayRotation rotation, WindowInfo::PreRotation prerotation);
GPUDevice::PresentResult ApplyDisplayPostProcess(GPUTexture* target, GPUTexture* input,
const GSVector4i display_rect);
void DrawScreenQuad(const GSVector4i rect, const GSVector4 uv_rect, const GSVector2i target_size,
const GSVector2i final_target_size, DisplayRotation uv_rotation,
WindowInfo::PreRotation prerotation);
bool DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve);
void DestroyDeinterlaceTextures();

View file

@ -20,27 +20,6 @@ float2 ClampUV(float2 uv) {
})";
}
std::string GPUShaderGen::GenerateDisplayVertexShader() const
{
std::stringstream ss;
WriteHeader(ss);
WriteDisplayUniformBuffer(ss);
DeclareVertexEntryPoint(ss, {"float2 a_pos", "float2 a_tex0"}, 0, 1, {}, false, "", false, false, false);
ss << R"(
{
v_pos = float4(a_pos, 0.0f, 1.0f);
v_tex0 = a_tex0;
// NDC space Y flip in Vulkan.
#if API_VULKAN
v_pos.y = -v_pos.y;
#endif
}
)";
return std::move(ss).str();
}
std::string GPUShaderGen::GenerateDisplayFragmentShader(bool clamp_uv, bool nearest) const
{
std::stringstream ss;

View file

@ -396,7 +396,8 @@ void GPU_SW::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd)
const u32 line_skip = BoolToUInt32(cmd->interlaced_display_interleaved);
const u32 src_x = is_24bit ? cmd->X : cmd->display_vram_left;
const u32 skip_x = is_24bit ? (cmd->display_vram_left - cmd->X) : 0;
const u32 src_y = cmd->display_vram_top;
const u32 src_y = cmd->display_vram_top +
(BoolToUInt8(cmd->interlaced_display_field) & BoolToUInt8(cmd->interlaced_display_interleaved));
const u32 width = cmd->display_vram_width;
const u32 height = cmd->display_vram_height;

View file

@ -621,7 +621,7 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_
if (g_gpu_settings.gpu_disable_compressed_textures)
disabled_features |= GPUDevice::FEATURE_MASK_COMPRESSED_TEXTURES;
// Don't dump shaders on debug builds for Android, users will complain about storage...
// Don't dump shaders on debug builds for Android, users will complain about storage...
#if !defined(__ANDROID__) || defined(_DEBUG)
const std::string_view shader_dump_directory(EmuFolders::DataRoot);
#else
@ -636,9 +636,9 @@ bool GPUThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool clear_
Host::GetStringSettingValue("GPU", "Adapter"), static_cast<GPUDevice::FeatureMask>(disabled_features),
shader_dump_directory,
g_gpu_settings.gpu_disable_shader_cache ? std::string_view() : std::string_view(EmuFolders::Cache),
SHADER_CACHE_VERSION, g_gpu_settings.gpu_use_debug_device, wi.value(), s_state.requested_vsync,
s_state.requested_allow_present_throttle, fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr,
exclusive_fullscreen_control, &create_error))
SHADER_CACHE_VERSION, g_gpu_settings.gpu_use_debug_device, g_gpu_settings.gpu_use_debug_device_gpu_validation,
wi.value(), s_state.requested_vsync, s_state.requested_allow_present_throttle,
fullscreen_mode.has_value() ? &fullscreen_mode.value() : nullptr, exclusive_fullscreen_control, &create_error))
{
ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
if (g_gpu_device)
@ -1270,8 +1270,9 @@ void GPUThread::DisplayWindowResizedOnThread()
if (!swap_chain)
return;
const float f_width = static_cast<float>(swap_chain->GetWidth());
const float f_height = static_cast<float>(swap_chain->GetHeight());
// our imgui stuff can't cope with 0x0/hidden windows
const float f_width = static_cast<float>(std::max(swap_chain->GetWidth(), 1u));
const float f_height = static_cast<float>(std::max(swap_chain->GetHeight(), 1u));
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);

View file

@ -791,9 +791,9 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last)
}
else
{
precise_x = static_cast<float>(x) / 4096.0f;
precise_x = static_cast<float>(x) / (static_cast<float>(1 << shift));
precise_y = static_cast<float>(y) / (static_cast<float>(1 << shift));
precise_z = static_cast<float>(z) / (static_cast<float>(1 << shift));
precise_z = static_cast<float>(z) / 4096.0f;
}
precise_sz3 = precise_z;

View file

@ -11,7 +11,8 @@ enum : u32
{
NUM_DATA_REGS = 32,
NUM_CONTROL_REGS = 32,
NUM_REGS = NUM_DATA_REGS + NUM_CONTROL_REGS
NUM_REGS = NUM_DATA_REGS + NUM_CONTROL_REGS,
MAX_Z = 65535,
};
union FLAGS

View file

@ -508,8 +508,8 @@ void ImGuiManager::DrawEnhancementsOverlay(const GPUBackend* gpu)
text.append_format(" {}x{}", g_gpu_settings.gpu_multisamples,
g_gpu_settings.gpu_per_sample_shading ? "SSAA" : "MSAA");
}
if (g_gpu_settings.gpu_true_color)
text.append(" TrueCol");
if (g_gpu_settings.gpu_dithering_mode != GPUDitheringMode::Unscaled)
text.append_format(" DT={}", Settings::GetGPUDitheringModeName(g_gpu_settings.gpu_dithering_mode));
text.append_format(" DI={}", Settings::GetDisplayDeinterlacingModeName(g_gpu_settings.display_deinterlacing_mode));
if (g_settings.gpu_force_video_timing == ForceVideoTimingMode::NTSC && System::GetRegion() == ConsoleRegion::PAL)
text.append(" PAL60");
@ -626,7 +626,8 @@ void ImGuiManager::DrawFrameTimeOverlay(float& position_y, float scale, float ma
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
if (ImGui::Begin("##frame_times", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs))
if (ImGui::Begin("##frame_times", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoFocusOnAppearing))
{
ImGui::PushFont(fixed_font);
@ -1095,6 +1096,9 @@ void SaveStateSelectorUI::InitializePlaceholderListEntry(ListEntry* li, const st
void SaveStateSelectorUI::Draw()
{
using ImGuiFullscreen::DarkerColor;
using ImGuiFullscreen::UIStyle;
static constexpr float SCROLL_ANIMATION_TIME = 0.25f;
static constexpr float BG_ANIMATION_TIME = 0.15f;
@ -1106,7 +1110,11 @@ void SaveStateSelectorUI::Draw()
const float padding_and_rounding = 18.0f * scale;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, padding_and_rounding);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(padding_and_rounding, padding_and_rounding));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.11f, 0.15f, 0.17f, 0.8f));
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0.0f);
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, UIStyle.PrimaryColor);
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, UIStyle.BackgroundColor);
ImGui::PushStyleColor(ImGuiCol_WindowBg, DarkerColor(UIStyle.PopupBackgroundColor));
ImGui::PushStyleColor(ImGuiCol_Text, UIStyle.BackgroundTextColor);
ImGui::PushFont(ImGuiManager::GetOSDFont());
ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), ImGuiCond_Always,
@ -1139,6 +1147,7 @@ void SaveStateSelectorUI::Draw()
if (entry.slot == current_slot && entry.global == current_slot_global)
{
ImGui::SetCursorPosY(y_start);
ImGui::PushStyleColor(ImGuiCol_Text, UIStyle.PrimaryTextColor);
const ImVec2 p_start(ImGui::GetCursorScreenPos());
const ImVec2 p_end(p_start.x + item_width, p_start.y + item_height);
@ -1171,7 +1180,7 @@ void SaveStateSelectorUI::Draw()
ImGui::GetWindowDrawList()->AddRectFilled(highlight_pos,
ImVec2(highlight_pos.x + item_width, highlight_pos.y + item_height),
ImColor(0.22f, 0.30f, 0.34f, 0.9f), padding_and_rounding);
ImGui::GetColorU32(UIStyle.PrimaryColor), padding_and_rounding);
}
if (GPUTexture* preview_texture =
@ -1201,6 +1210,9 @@ void SaveStateSelectorUI::Draw()
ImGui::Unindent(text_indent);
ImGui::SetCursorPosY(y_start);
ImGui::ItemSize(ImVec2(item_width, item_height));
if (entry.slot == current_slot && entry.global == current_slot_global)
ImGui::PopStyleColor();
}
}
ImGui::EndChild();
@ -1229,8 +1241,8 @@ void SaveStateSelectorUI::Draw()
ImGui::End();
ImGui::PopFont();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(4);
// auto-close
s_state.open_time += io.DeltaTime;

View file

@ -79,17 +79,18 @@ static bool CloseFileHandle(u32 handle)
static std::string ResolveHostPath(const std::string& path)
{
// Double-check that it falls within the directory of the elf.
// Double-check that it falls within the directory of the root.
// Not a real sandbox, but emulators shouldn't be treated as such. Don't run untrusted code!
const std::string& root = g_settings.pcdrv_root;
std::string canonicalized_path = Path::Canonicalize(Path::Combine(root, path));
std::string canonicalized_path =
Path::IsAbsolute(path) ? Path::Canonicalize(path) : Path::Canonicalize(Path::Combine(root, path));
if (canonicalized_path.length() < root.length() || // Length has to be longer (a file),
!canonicalized_path.starts_with(root) || // and start with the host root,
canonicalized_path[root.length()] != FS_OSPATH_SEPARATOR_CHARACTER) // and we can't access a sibling.
{
ERROR_LOG("Denying access to path outside of PCDrv directory. Requested path: '{}', "
"Resolved path: '{}', Root directory: '{}'",
path, root, canonicalized_path);
path, canonicalized_path, root);
canonicalized_path.clear();
}

View file

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@ -6,7 +6,7 @@
#include "common/types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 79;
static constexpr u32 SAVE_STATE_VERSION = 81;
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View file

@ -4,6 +4,7 @@
#include "settings.h"
#include "achievements.h"
#include "controller.h"
#include "gte_types.h"
#include "host.h"
#include "imgui_overlays.h"
#include "system.h"
@ -90,7 +91,20 @@ float SettingInfo::FloatStepValue() const
return step_value ? StringUtil::FromChars<float>(step_value).value_or(fallback_value) : fallback_value;
}
GPUSettings::GPUSettings() = default;
GPUSettings::GPUSettings()
{
SetPGXPDepthClearThreshold(DEFAULT_GPU_PGXP_DEPTH_THRESHOLD);
}
float GPUSettings::GetPGXPDepthClearThreshold() const
{
return gpu_pgxp_depth_clear_threshold * static_cast<float>(GTE::MAX_Z);
}
void GPUSettings::SetPGXPDepthClearThreshold(float value)
{
gpu_pgxp_depth_clear_threshold = value / static_cast<float>(GTE::MAX_Z);
}
#ifdef DYNAMIC_HOST_PAGE_SIZE
// See note in settings.h - 16K ends up faster with LUT because of nearby code/data.
@ -202,6 +216,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_automatic_resolution_scale = (gpu_resolution_scale == 0);
gpu_multisamples = static_cast<u8>(si.GetUIntValue("GPU", "Multisamples", 1u));
gpu_use_debug_device = si.GetBoolValue("GPU", "UseDebugDevice", false);
gpu_use_debug_device_gpu_validation = si.GetBoolValue("GPU", "UseGPUBasedValidation", false);
gpu_disable_shader_cache = si.GetBoolValue("GPU", "DisableShaderCache", false);
gpu_disable_dual_source_blend = si.GetBoolValue("GPU", "DisableDualSourceBlend", false);
gpu_disable_framebuffer_fetch = si.GetBoolValue("GPU", "DisableFramebufferFetch", false);
@ -215,10 +230,8 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
gpu_max_queued_frames = static_cast<u8>(si.GetUIntValue("GPU", "MaxQueuedFrames", DEFAULT_GPU_MAX_QUEUED_FRAMES));
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
gpu_true_color = si.GetBoolValue("GPU", "TrueColor", true);
gpu_scaled_dithering = si.GetBoolValue("GPU", "ScaledDithering", true);
gpu_scaled_interlacing = si.GetBoolValue("GPU", "ScaledInterlacing", true);
gpu_force_round_texcoords = si.GetBoolValue("GPU", "ForceRoundTextureCoordinates", false);
gpu_accurate_blending = si.GetBoolValue("GPU", "AccurateBlending", false);
gpu_texture_filter =
ParseTextureFilterName(
si.GetStringValue("GPU", "TextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str())
@ -227,6 +240,10 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
ParseTextureFilterName(
si.GetStringValue("GPU", "SpriteTextureFilter", GetTextureFilterName(DEFAULT_GPU_TEXTURE_FILTER)).c_str())
.value_or(DEFAULT_GPU_TEXTURE_FILTER);
gpu_dithering_mode =
ParseGPUDitheringModeName(
si.GetStringValue("GPU", "DitheringMode", GetGPUDitheringModeName(DEFAULT_GPU_DITHERING_MODE)).c_str())
.value_or(DEFAULT_GPU_DITHERING_MODE);
gpu_line_detect_mode =
ParseLineDetectModeName(
si.GetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(DEFAULT_GPU_LINE_DETECT_MODE)).c_str())
@ -258,7 +275,7 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
gpu_pgxp_depth_buffer = si.GetBoolValue("GPU", "PGXPDepthBuffer", false);
gpu_pgxp_disable_2d = si.GetBoolValue("GPU", "PGXPDisableOn2DPolygons", false);
gpu_pgxp_transparent_depth = si.GetBoolValue("GPU", "PGXPTransparentDepthTest", false);
SetPGXPDepthClearThreshold(si.GetFloatValue("GPU", "PGXPDepthClearThreshold", DEFAULT_GPU_PGXP_DEPTH_THRESHOLD));
SetPGXPDepthClearThreshold(si.GetFloatValue("GPU", "PGXPDepthThreshold", DEFAULT_GPU_PGXP_DEPTH_THRESHOLD));
gpu_show_vram = si.GetBoolValue("Debug", "ShowVRAM");
gpu_dump_cpu_to_vram_copies = si.GetBoolValue("Debug", "DumpCPUToVRAMCopies");
gpu_dump_vram_to_cpu_copies = si.GetBoolValue("Debug", "DumpVRAMToCPUCopies");
@ -359,7 +376,10 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
Truncate8(std::min<u32>(si.GetUIntValue("CDROM", "ReadSpeedup", 1u), std::numeric_limits<u8>::max()));
cdrom_seek_speedup =
Truncate8(std::min<u32>(si.GetUIntValue("CDROM", "SeekSpeedup", 1u), std::numeric_limits<u8>::max()));
cdrom_max_speedup_cycles = si.GetUIntValue("CDROM", "MaxSpeedupCycles", DEFAULT_CDROM_MAX_SPEEDUP_CYCLES);
cdrom_max_seek_speedup_cycles =
std::max(si.GetUIntValue("CDROM", "MaxSeekSpeedupCycles", DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES), 1u);
cdrom_max_read_speedup_cycles =
std::max(si.GetUIntValue("CDROM", "MaxReadSpeedupCycles", DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES), 1u);
audio_backend =
AudioStream::ParseBackendName(
@ -557,6 +577,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
if (!ignore_base)
{
si.SetBoolValue("GPU", "UseDebugDevice", gpu_use_debug_device);
si.SetBoolValue("GPU", "UseGPUBasedValidation", gpu_use_debug_device_gpu_validation);
si.SetBoolValue("GPU", "DisableShaderCache", gpu_disable_shader_cache);
si.SetBoolValue("GPU", "DisableDualSourceBlend", gpu_disable_dual_source_blend);
si.SetBoolValue("GPU", "DisableFramebufferFetch", gpu_disable_framebuffer_fetch);
@ -572,12 +593,11 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetUIntValue("GPU", "MaxQueuedFrames", gpu_max_queued_frames);
si.SetBoolValue("GPU", "UseThread", gpu_use_thread);
si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", gpu_use_software_renderer_for_readbacks);
si.SetBoolValue("GPU", "TrueColor", gpu_true_color);
si.SetBoolValue("GPU", "ScaledDithering", gpu_scaled_dithering);
si.SetBoolValue("GPU", "ScaledInterlacing", gpu_scaled_interlacing);
si.SetBoolValue("GPU", "ForceRoundTextureCoordinates", gpu_force_round_texcoords);
si.SetBoolValue("GPU", "AccurateBlending", gpu_accurate_blending);
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
si.SetStringValue("GPU", "SpriteTextureFilter", GetTextureFilterName(gpu_sprite_texture_filter));
si.SetStringValue("GPU", "DitheringMode", GetGPUDitheringModeName(gpu_dithering_mode));
si.SetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(gpu_line_detect_mode));
si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode));
si.SetUIntValue("GPU", "DownsampleScale", gpu_downsample_scale);
@ -597,7 +617,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetBoolValue("GPU", "PGXPDepthBuffer", gpu_pgxp_depth_buffer);
si.SetBoolValue("GPU", "PGXPDisableOn2DPolygons", gpu_pgxp_disable_2d);
si.SetBoolValue("GPU", "PGXPTransparentDepthTest", gpu_pgxp_transparent_depth);
si.SetFloatValue("GPU", "PGXPDepthClearThreshold", GetPGXPDepthClearThreshold());
si.SetFloatValue("GPU", "PGXPDepthThreshold", GetPGXPDepthClearThreshold());
si.SetBoolValue("Debug", "ShowVRAM", gpu_show_vram);
si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", gpu_dump_cpu_to_vram_copies);
si.SetBoolValue("Debug", "DumpVRAMToCPUCopies", gpu_dump_vram_to_cpu_copies);
@ -656,7 +676,8 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetBoolValue("CDROM", "MuteCDAudio", cdrom_mute_cd_audio);
si.SetUIntValue("CDROM", "ReadSpeedup", cdrom_read_speedup);
si.SetUIntValue("CDROM", "SeekSpeedup", cdrom_seek_speedup);
si.SetUIntValue("CDROM", "MaxSpeedupCycles", cdrom_max_speedup_cycles);
si.SetUIntValue("CDROM", "MaxReadSpeedupCycles", cdrom_max_seek_speedup_cycles);
si.SetUIntValue("CDROM", "MaxSeekSpeedupCycles", cdrom_max_read_speedup_cycles);
si.SetStringValue("Audio", "Backend", AudioStream::GetBackendName(audio_backend));
si.SetStringValue("Audio", "Driver", audio_driver.c_str());
@ -975,11 +996,11 @@ void Settings::FixIncompatibleSettings(const SettingsInterface& si, bool display
g_settings.gpu_multisamples = 1;
g_settings.gpu_automatic_resolution_scale = false;
g_settings.gpu_per_sample_shading = false;
g_settings.gpu_true_color = false;
g_settings.gpu_scaled_dithering = false;
g_settings.gpu_scaled_interlacing = false;
g_settings.gpu_force_round_texcoords = false;
g_settings.gpu_texture_filter = GPUTextureFilter::Nearest;
g_settings.gpu_sprite_texture_filter = GPUTextureFilter::Nearest;
g_settings.gpu_dithering_mode = GPUDitheringMode::Unscaled;
g_settings.gpu_line_detect_mode = GPULineDetectMode::Disabled;
g_settings.gpu_force_video_timing = ForceVideoTimingMode::Disabled;
g_settings.gpu_widescreen_hack = false;
@ -1108,6 +1129,7 @@ bool Settings::AreGPUDeviceSettingsChanged(const Settings& old_settings) const
{
return (gpu_adapter != old_settings.gpu_adapter || gpu_use_thread != old_settings.gpu_use_thread ||
gpu_use_debug_device != old_settings.gpu_use_debug_device ||
gpu_use_debug_device_gpu_validation != old_settings.gpu_use_debug_device_gpu_validation ||
gpu_disable_shader_cache != old_settings.gpu_disable_shader_cache ||
gpu_disable_dual_source_blend != old_settings.gpu_disable_dual_source_blend ||
gpu_disable_framebuffer_fetch != old_settings.gpu_disable_framebuffer_fetch ||
@ -1541,6 +1563,45 @@ const char* Settings::GetTextureFilterDisplayName(GPUTextureFilter filter)
"GPUTextureFilter");
}
static constexpr const std::array s_gpu_dithering_mode_names = {
"Unscaled", "UnscaledShaderBlend", "Scaled", "ScaledShaderBlend", "TrueColor", "TrueColorFull",
};
static constexpr const std::array s_gpu_dithering_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "Unscaled", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Unscaled (Shader Blending)", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Scaled", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Scaled (Shader Blending)", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "True Color", "GPUDitheringMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "True Color (Full)", "GPUDitheringMode"),
};
static_assert(s_gpu_dithering_mode_names.size() == static_cast<size_t>(GPUDitheringMode::MaxCount));
static_assert(s_gpu_dithering_mode_display_names.size() == static_cast<size_t>(GPUDitheringMode::MaxCount));
std::optional<GPUDitheringMode> Settings::ParseGPUDitheringModeName(const char* str)
{
int index = 0;
for (const char* name : s_gpu_dithering_mode_names)
{
if (StringUtil::Strcasecmp(name, str) == 0)
return static_cast<GPUDitheringMode>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetGPUDitheringModeName(GPUDitheringMode mode)
{
return s_gpu_dithering_mode_names[static_cast<size_t>(mode)];
}
const char* Settings::GetGPUDitheringModeDisplayName(GPUDitheringMode mode)
{
return Host::TranslateToCString("Settings", s_gpu_dithering_mode_display_names[static_cast<size_t>(mode)],
"GPUDitheringMode");
}
static constexpr const std::array s_line_detect_mode_names = {
"Disabled",
"Quads",
@ -1873,7 +1934,7 @@ static constexpr const std::array s_display_force_video_timing_names = {
};
static constexpr const std::array s_display_force_video_timing_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "Disabled", "ForceVideoTiming"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Auto-Detect", "ForceVideoTiming"),
TRANSLATE_DISAMBIG_NOOP("Settings", "NTSC (60hz)", "ForceVideoTiming"),
TRANSLATE_DISAMBIG_NOOP("Settings", "PAL (50hz)", "ForceVideoTiming"),
};

View file

@ -69,6 +69,7 @@ struct GPUSettings
ForceVideoTimingMode gpu_force_video_timing = DEFAULT_FORCE_VIDEO_TIMING_MODE;
GPUTextureFilter gpu_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
GPUTextureFilter gpu_sprite_texture_filter = DEFAULT_GPU_TEXTURE_FILTER;
GPUDitheringMode gpu_dithering_mode = DEFAULT_GPU_DITHERING_MODE;
GPULineDetectMode gpu_line_detect_mode = DEFAULT_GPU_LINE_DETECT_MODE;
GPUDownsampleMode gpu_downsample_mode = DEFAULT_GPU_DOWNSAMPLE_MODE;
u8 gpu_downsample_scale = 1;
@ -93,6 +94,7 @@ struct GPUSettings
bool gpu_use_thread : 1 = true;
bool gpu_use_software_renderer_for_readbacks : 1 = false;
bool gpu_use_debug_device : 1 = false;
bool gpu_use_debug_device_gpu_validation : 1 = false;
bool gpu_disable_shader_cache : 1 = false;
bool gpu_disable_dual_source_blend : 1 = false;
bool gpu_disable_framebuffer_fetch : 1 = false;
@ -104,10 +106,8 @@ struct GPUSettings
bool gpu_disable_compressed_textures : 1 = false;
bool gpu_automatic_resolution_scale : 1 = false;
bool gpu_per_sample_shading : 1 = false;
bool gpu_true_color : 1 = true;
bool gpu_scaled_dithering : 1 = true;
bool gpu_scaled_interlacing : 1 = true;
bool gpu_force_round_texcoords : 1 = false;
bool gpu_accurate_blending : 1 = false;
bool gpu_widescreen_hack : 1 = false;
bool gpu_texture_cache : 1 = false;
bool gpu_show_vram : 1 = false;
@ -150,7 +150,7 @@ struct GPUSettings
float display_osd_scale = DEFAULT_OSD_SCALE;
float display_osd_margin = 0.0f;
float gpu_pgxp_tolerance = -1.0f;
float gpu_pgxp_depth_clear_threshold = DEFAULT_GPU_PGXP_DEPTH_THRESHOLD / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
float gpu_pgxp_depth_clear_threshold = 0.0f;
// texture replacements
struct TextureReplacementSettings
@ -209,9 +209,22 @@ struct GPUSettings
std::string overlay_image_path;
float GetDisplayAspectRatioValue() const;
float GetPGXPDepthClearThreshold() const;
void SetPGXPDepthClearThreshold(float value);
ALWAYS_INLINE bool IsUsingSoftwareRenderer() const { return (gpu_renderer == GPURenderer::Software); }
ALWAYS_INLINE bool IsUsingAccurateBlending() const { return (gpu_accurate_blending && !gpu_true_color); }
ALWAYS_INLINE bool IsUsingTrueColor() const { return (gpu_dithering_mode >= GPUDitheringMode::TrueColor); }
ALWAYS_INLINE bool IsUsingDithering() const { return (gpu_dithering_mode < GPUDitheringMode::TrueColor); }
ALWAYS_INLINE bool IsUsingShaderBlending() const
{
return (gpu_dithering_mode == GPUDitheringMode::UnscaledShaderBlend ||
gpu_dithering_mode == GPUDitheringMode::ScaledShaderBlend);
}
ALWAYS_INLINE bool IsUsingScaledDithering() const
{
return (gpu_dithering_mode == GPUDitheringMode::Scaled ||
gpu_dithering_mode == GPUDitheringMode::ScaledShaderBlend);
}
ALWAYS_INLINE bool IsUsingIntegerDisplayScaling() const
{
return (display_scaling == DisplayScalingMode::NearestInteger ||
@ -220,23 +233,16 @@ struct GPUSettings
ALWAYS_INLINE bool UsingPGXPCPUMode() const { return gpu_pgxp_enable && gpu_pgxp_cpu; }
ALWAYS_INLINE bool UsingPGXPDepthBuffer() const { return gpu_pgxp_enable && gpu_pgxp_depth_buffer; }
ALWAYS_INLINE float GetPGXPDepthClearThreshold() const
{
return gpu_pgxp_depth_clear_threshold * GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
ALWAYS_INLINE void SetPGXPDepthClearThreshold(float value)
{
gpu_pgxp_depth_clear_threshold = value / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
}
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic;
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
static constexpr GPUDitheringMode DEFAULT_GPU_DITHERING_MODE = GPUDitheringMode::TrueColor;
static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::Disabled;
static constexpr GPUDownsampleMode DEFAULT_GPU_DOWNSAMPLE_MODE = GPUDownsampleMode::Disabled;
static constexpr GPUWireframeMode DEFAULT_GPU_WIREFRAME_MODE = GPUWireframeMode::Disabled;
static constexpr GPUDumpCompressionMode DEFAULT_GPU_DUMP_COMPRESSION_MODE = GPUDumpCompressionMode::ZstDefault;
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 300.0f;
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 4096.0f;
static constexpr float DEFAULT_GPU_PGXP_DEPTH_THRESHOLD = 4096.0f;
static constexpr float GPU_PGXP_DEPTH_THRESHOLD_SCALE = 65536.0f;
static constexpr DisplayDeinterlacingMode DEFAULT_DISPLAY_DEINTERLACING_MODE = DisplayDeinterlacingMode::Progressive;
static constexpr DisplayCropMode DEFAULT_DISPLAY_CROP_MODE = DisplayCropMode::Overscan;
@ -310,7 +316,8 @@ struct Settings : public GPUSettings
u8 cdrom_read_speedup = 1;
u8 cdrom_seek_speedup = 1;
u32 cdrom_max_speedup_cycles = DEFAULT_CDROM_MAX_SPEEDUP_CYCLES;
u32 cdrom_max_seek_speedup_cycles = DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES;
u32 cdrom_max_read_speedup_cycles = DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES;
u8 cdrom_readahead_sectors = DEFAULT_CDROM_READAHEAD_SECTORS;
CDROMMechaconVersion cdrom_mechacon_version = DEFAULT_CDROM_MECHACON_VERSION;
@ -479,6 +486,10 @@ struct Settings : public GPUSettings
static const char* GetTextureFilterName(GPUTextureFilter filter);
static const char* GetTextureFilterDisplayName(GPUTextureFilter filter);
static std::optional<GPUDitheringMode> ParseGPUDitheringModeName(const char* str);
static const char* GetGPUDitheringModeName(GPUDitheringMode mode);
static const char* GetGPUDitheringModeDisplayName(GPUDitheringMode mode);
static std::optional<GPULineDetectMode> ParseLineDetectModeName(const char* str);
static const char* GetLineDetectModeName(GPULineDetectMode filter);
static const char* GetLineDetectModeDisplayName(GPULineDetectMode filter);
@ -575,7 +586,8 @@ struct Settings : public GPUSettings
#endif
static constexpr u8 DEFAULT_CDROM_READAHEAD_SECTORS = 8;
static constexpr u32 DEFAULT_CDROM_MAX_SPEEDUP_CYCLES = 30000;
static constexpr u32 DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES = 30000;
static constexpr u32 DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES = 30000;
static constexpr CDROMMechaconVersion DEFAULT_CDROM_MECHACON_VERSION = CDROMMechaconVersion::VC1A;
static constexpr ControllerType DEFAULT_CONTROLLER_1_TYPE = ControllerType::AnalogController;

View file

@ -5,4 +5,4 @@
#include "common/types.h"
static constexpr u32 SHADER_CACHE_VERSION = 28;
static constexpr u32 SHADER_CACHE_VERSION = 29;

View file

@ -800,6 +800,15 @@ bool System::IsUsingPS2BIOS()
return (s_state.bios_image_info && s_state.bios_image_info->fastboot_patch == BIOS::ImageInfo::FastBootPatch::Type2);
}
bool System::IsDiscPath(std::string_view path)
{
return (StringUtil::EndsWithNoCase(path, ".bin") || StringUtil::EndsWithNoCase(path, ".cue") ||
StringUtil::EndsWithNoCase(path, ".img") || StringUtil::EndsWithNoCase(path, ".iso") ||
StringUtil::EndsWithNoCase(path, ".chd") || StringUtil::EndsWithNoCase(path, ".ecm") ||
StringUtil::EndsWithNoCase(path, ".mds") || StringUtil::EndsWithNoCase(path, ".pbp") ||
StringUtil::EndsWithNoCase(path, ".m3u"));
}
bool System::IsExePath(std::string_view path)
{
return (StringUtil::EndsWithNoCase(path, ".exe") || StringUtil::EndsWithNoCase(path, ".psexe") ||
@ -820,22 +829,7 @@ bool System::IsGPUDumpPath(std::string_view path)
bool System::IsLoadablePath(std::string_view path)
{
static constexpr const std::array extensions = {
".bin", ".cue", ".img", ".iso", ".chd", ".ecm", ".mds", // discs
".exe", ".psexe", ".ps-exe", ".psx", ".cpe", ".elf", // exes
".psf", ".minipsf", // psf
".psxgpu", ".psxgpu.zst", ".psxgpu.xz", // gpu dump
".m3u", // playlists
".pbp",
};
for (const char* test_extension : extensions)
{
if (StringUtil::EndsWithNoCase(path, test_extension))
return true;
}
return false;
return (IsDiscPath(path) || IsExePath(path) || IsPsfPath(path) || IsGPUDumpPath(path));
}
bool System::IsSaveStatePath(std::string_view path)
@ -1597,6 +1591,8 @@ void System::PauseSystem(bool paused)
if (paused)
{
FullscreenUI::OnSystemPaused();
InputManager::PauseVibration();
InputManager::UpdateHostMouseMode();
@ -4477,12 +4473,11 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.gpu_max_queued_frames != old_settings.gpu_max_queued_frames ||
g_settings.gpu_use_software_renderer_for_readbacks !=
old_settings.gpu_use_software_renderer_for_readbacks ||
g_settings.gpu_true_color != old_settings.gpu_true_color ||
g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering ||
g_settings.gpu_scaled_interlacing != old_settings.gpu_scaled_interlacing ||
g_settings.gpu_force_round_texcoords != old_settings.gpu_force_round_texcoords ||
g_settings.gpu_accurate_blending != old_settings.gpu_accurate_blending ||
g_settings.gpu_texture_filter != old_settings.gpu_texture_filter ||
g_settings.gpu_sprite_texture_filter != old_settings.gpu_sprite_texture_filter ||
g_settings.gpu_dithering_mode != old_settings.gpu_dithering_mode ||
g_settings.gpu_line_detect_mode != old_settings.gpu_line_detect_mode ||
g_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode ||
g_settings.gpu_downsample_scale != old_settings.gpu_downsample_scale ||
@ -4541,7 +4536,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
g_settings.display_auto_resize_window != old_settings.display_auto_resize_window ||
g_settings.display_screenshot_mode != old_settings.display_screenshot_mode ||
g_settings.display_screenshot_format != old_settings.display_screenshot_format ||
g_settings.display_screenshot_quality != old_settings.display_screenshot_quality)
g_settings.display_screenshot_quality != old_settings.display_screenshot_quality ||
g_settings.gpu_pgxp_depth_clear_threshold != old_settings.gpu_pgxp_depth_clear_threshold)
{
if (device_settings_changed)
{
@ -4787,7 +4783,7 @@ void System::WarnAboutUnsafeSettings()
if (g_settings.cdrom_read_speedup != 1 || g_settings.cdrom_seek_speedup != 1)
append(ICON_EMOJI_WARNING, TRANSLATE_SV("System", "CD-ROM read/seek speedup is enabled. This may crash games."));
if (g_settings.gpu_force_video_timing != ForceVideoTimingMode::Disabled)
append(ICON_FA_TV, TRANSLATE_SV("System", "Force frame timings is enabled. Games may run at incorrect speeds."));
append(ICON_FA_TV, TRANSLATE_SV("System", "Frame rate is not set to automatic. Games may run at incorrect speeds."));
if (!g_settings.IsUsingSoftwareRenderer())
{
if (g_settings.gpu_multisamples != 1)
@ -4851,8 +4847,8 @@ void System::WarnAboutUnsafeSettings()
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Resolution scale set to 1x."));
if (g_settings.gpu_multisamples != 1)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Multisample anti-aliasing disabled."));
if (g_settings.gpu_true_color)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "True color disabled."));
if (g_settings.gpu_dithering_mode != GPUDitheringMode::Unscaled)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Dithering set to unscaled."));
if (g_settings.gpu_texture_filter != GPUTextureFilter::Nearest ||
g_settings.gpu_sprite_texture_filter != GPUTextureFilter::Nearest)
{

View file

@ -118,6 +118,9 @@ enum class Taint : u8
MaxCount,
};
/// Returns true if the path is a disc image that we can load.
bool IsDiscPath(std::string_view path);
/// Returns true if the path is a PlayStation executable we can inject.
bool IsExePath(std::string_view path);

View file

@ -100,6 +100,18 @@ enum class GPUTextureFilter : u8
Count
};
enum class GPUDitheringMode : u8
{
Unscaled,
UnscaledShaderBlend,
Scaled,
ScaledShaderBlend,
TrueColor,
TrueColorFull,
MaxCount,
};
enum class GPUDownsampleMode : u8
{
Disabled,

View file

@ -861,7 +861,9 @@ void MiniHost::ProcessSDLEvent(const SDL_Event* ev)
{
if (ev->button.button > 0)
{
Host::RunOnCPUThread([button = ev->button.button - 1, pressed = (ev->type == SDL_EVENT_MOUSE_BUTTON_DOWN)]() {
// swap middle/right because sdl orders them differently
const u8 button = (ev->button.button == 3) ? 1 : ((ev->button.button == 2) ? 2 : (ev->button.button - 1));
Host::RunOnCPUThread([button, pressed = (ev->type == SDL_EVENT_MOUSE_BUTTON_DOWN)]() {
InputManager::InvokeEvents(InputManager::MakePointerButtonKey(0, button), pressed ? 1.0f : 0.0f,
GenericInputBinding::Unknown);
});
@ -1223,6 +1225,7 @@ void Host::RefreshGameListAsync(bool invalidate_cache)
std::unique_lock lock(s_state.state_mutex);
delete s_state.game_list_refresh_progress;
s_state.game_list_refresh_progress = nullptr;
});
}
@ -1251,6 +1254,32 @@ std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
return MiniHost::TranslateSDLWindowInfo(MiniHost::s_state.sdl_window, nullptr);
}
void Host::RequestResetSettings(bool system, bool controller)
{
using namespace MiniHost;
auto lock = Host::GetSettingsLock();
{
SettingsInterface& si = *s_state.base_settings_interface.get();
if (system)
{
System::SetDefaultSettings(si);
EmuFolders::SetDefaults();
EmuFolders::Save(si);
}
if (controller)
{
InputManager::SetDefaultSourceConfig(si);
Settings::SetDefaultControllerConfig(si);
Settings::SetDefaultHotkeyConfig(si);
}
}
System::ApplySettings(false);
}
void Host::RequestExitApplication(bool allow_confirm)
{
Host::RunOnCPUThread([]() {
@ -1424,6 +1453,11 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo
callback(std::string());
}
const char* Host::GetDefaultFullscreenUITheme()
{
return "";
}
bool Host::ShouldPreferHostFileSelector()
{
return false;

View file

@ -45,7 +45,8 @@ static QCheckBox* setBooleanTweakOption(QTableWidget* table, int row, bool value
}
static QSpinBox* addIntRangeTweakOption(SettingsWindow* dialog, QTableWidget* table, QString name, std::string section,
std::string key, int min_value, int max_value, int default_value)
std::string key, int min_value, int max_value, int default_value,
const QString& suffix = QString())
{
const int row = table->rowCount();
@ -58,6 +59,9 @@ static QSpinBox* addIntRangeTweakOption(SettingsWindow* dialog, QTableWidget* ta
QSpinBox* cb = new QSpinBox(table);
cb->setMinimum(min_value);
cb->setMaximum(max_value);
if (!suffix.isEmpty())
cb->setSuffix(suffix);
if (!section.empty() || !key.empty())
{
SettingWidgetBinder::BindWidgetToIntSetting(dialog->getSettingsInterface(), cb, std::move(section), std::move(key),
@ -254,13 +258,13 @@ void AdvancedSettingsWidget::addTweakOptions()
}
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Max Slice Ticks"), "Hacks", "DMAMaxSliceTicks", 1,
10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS);
10000, Settings::DEFAULT_DMA_MAX_SLICE_TICKS, tr(" cycles"));
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("DMA Halt Ticks"), "Hacks", "DMAHaltTicks", 1, 10000,
Settings::DEFAULT_DMA_HALT_TICKS);
Settings::DEFAULT_DMA_HALT_TICKS, tr(" cycles"));
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU FIFO Size"), "Hacks", "GPUFIFOSize", 16, 4096,
Settings::DEFAULT_GPU_FIFO_SIZE);
Settings::DEFAULT_GPU_FIFO_SIZE, tr(" words"));
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("GPU Max Run-Ahead"), "Hacks", "GPUMaxRunAhead", 0, 1000,
Settings::DEFAULT_GPU_MAX_RUN_AHEAD);
Settings::DEFAULT_GPU_MAX_RUN_AHEAD, tr(" cycles"));
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Enable Recompiler Memory Exceptions"), "CPU",
"RecompilerMemoryExceptions", false);
@ -275,8 +279,14 @@ void AdvancedSettingsWidget::addTweakOptions()
Settings::ParseCDROMMechVersionName, Settings::GetCDROMMechVersionName,
Settings::GetCDROMMechVersionDisplayName, static_cast<u8>(CDROMMechaconVersion::Count),
Settings::DEFAULT_CDROM_MECHACON_VERSION);
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Speedup Read/Seek Cycles"), "CDROM",
"MaxSpeedupCycles", 0, 1000000, Settings::DEFAULT_CDROM_MAX_SPEEDUP_CYCLES);
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Readahead Sectors"), "CDROM", "ReadaheadSectors",
0, 32, Settings::DEFAULT_CDROM_READAHEAD_SECTORS, tr(" sectors"));
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Seek Speedup Cycles"), "CDROM",
"MaxSeekSpeedupCycles", 1, 1000000, Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES,
tr(" cycles"));
addIntRangeTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Max Read Speedup Cycles"), "CDROM",
"MaxReadSpeedupCycles", 1, 1000000, Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES,
tr(" cycles"));
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM Region Check"), "CDROM", "RegionCheck", false);
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("CD-ROM SubQ Skew"), "CDROM", "SubQSkew", false);
addBooleanTweakOption(m_dialog, m_ui.tweakOptionTable, tr("Allow Booting Without SBI File"), "CDROM",
@ -320,11 +330,15 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
setChoiceTweakOption(m_ui.tweakOptionTable, i++,
Settings::DEFAULT_CDROM_MECHACON_VERSION); // CDROM Mechacon Version
setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
Settings::DEFAULT_CDROM_MAX_SPEEDUP_CYCLES); // CD-ROM Max Speedup Read/Seek Cycles
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM SubQ Skew
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable GDB Server
Settings::DEFAULT_CDROM_READAHEAD_SECTORS); // CD-ROM Readahead Sectors
setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
Settings::DEFAULT_CDROM_MAX_SEEK_SPEEDUP_CYCLES); // CD-ROM Max Speedup Seek Cycles
setIntRangeTweakOption(m_ui.tweakOptionTable, i++,
Settings::DEFAULT_CDROM_MAX_READ_SPEEDUP_CYCLES); // CD-ROM Max Speedup Read Cycles
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM Region Check
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // CDROM SubQ Skew
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Allow booting without SBI file
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable GDB Server
setIntRangeTweakOption(m_ui.tweakOptionTable, i++, Settings::DEFAULT_GDB_SERVER_PORT); // GDB Server Port
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Export Shared Memory
setBooleanTweakOption(m_ui.tweakOptionTable, i++, false); // Enable PCDRV
@ -353,6 +367,7 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
sif->DeleteValue("CPU", "RecompilerBlockLinking");
sif->DeleteValue("CPU", "FastmemMode");
sif->DeleteValue("CDROM", "MechaconVersion");
sif->DeleteValue("CDROM", "ReadaheadSectors");
sif->DeleteValue("CDROM", "MaxSpeedupCycles");
sif->DeleteValue("CDROM", "RegionCheck");
sif->DeleteValue("CDROM", "SubQSkew");

Some files were not shown because too many files have changed in this diff Show more