Compare commits
130 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b798d8f1aa | ||
|
049ec4af90 | ||
|
162d742cc0 | ||
|
8c1f75ab8c | ||
|
d2c09dfaff | ||
|
3292833fb8 | ||
|
04c5a9f1b2 | ||
|
c5cc94c4b7 | ||
|
64513395f3 | ||
|
e6e6313219 | ||
|
4e2872f248 | ||
|
2d31bf5685 | ||
|
9bcd738f5c | ||
|
3bf5ded0d2 | ||
|
a3e840526a | ||
|
54c42f9c27 | ||
|
b4ae19411b | ||
|
37230107f4 | ||
|
730fa67952 | ||
|
3cf2f41723 | ||
|
ba3c693717 | ||
|
0687e59bda | ||
|
576658b6eb | ||
|
c7504e7016 | ||
|
e4a028834a | ||
|
f36062a94e | ||
|
9ef7f54f19 | ||
|
003518c234 | ||
|
6f2225fb61 | ||
|
f80a77fa27 | ||
|
ea3774cece | ||
|
58bd87666e | ||
|
fbadacae78 | ||
|
ceef7af1da | ||
|
ece18d10c5 | ||
|
ae9023ddf3 | ||
|
3f0abccb59 | ||
|
104c82c2c3 | ||
|
2cb6ce635f | ||
|
4ce001aef3 | ||
|
1fca8ae6bf | ||
|
3ffd20b833 | ||
|
9411e40c20 | ||
|
03d7af2efc | ||
|
9020959511 | ||
|
98d1c71981 | ||
|
741e971681 | ||
|
2d477f80b0 | ||
|
2311c8d287 | ||
|
32b09193d1 | ||
|
4afa41b927 | ||
|
3ab3e4be06 | ||
|
f4e93df1b1 | ||
|
115ba4433c | ||
|
74064af730 | ||
|
62d7a73381 | ||
|
d176109d3b | ||
|
6eddc0b982 | ||
|
346f0f945d | ||
|
de6a8fba51 | ||
|
5c41a48e39 | ||
|
19c03aff4d | ||
|
fffa44911c | ||
|
10fea83b4a | ||
|
eea4159756 | ||
|
e5cec05633 | ||
|
a1d69982f3 | ||
|
e3cb3d029b | ||
|
27fc7de07a | ||
|
a1e5bb7bc0 | ||
|
61de096d26 | ||
|
4f00a7c7c1 | ||
|
3420af829c | ||
|
830842891e | ||
|
dd885cfe2f | ||
|
8a0400ad2c | ||
|
bcd4b918dc | ||
|
c82c007a75 | ||
|
0a470067f5 | ||
|
9734066514 | ||
|
2a86faa51e | ||
|
4bac7cb79e | ||
|
db3b689166 | ||
|
1189f53840 | ||
|
6e27b8ab91 | ||
|
3d0f038697 | ||
|
6c867859bf | ||
|
f11c31b41c | ||
|
cffafad2ca | ||
|
b6fa2cecd3 | ||
|
9c971825a9 | ||
|
c57689cc90 | ||
|
511663198e | ||
|
531b3e103a | ||
|
e422afdec1 | ||
|
ba3295930c | ||
|
4fc49ee0ca | ||
|
f6296ceb09 | ||
|
9a9ed52b70 | ||
|
44af55a77f | ||
|
72a0ba1e8e | ||
|
aeadd4c280 | ||
|
d6ab840e4b | ||
|
492a55ee8b | ||
|
541af8d5de | ||
|
7bae23d79d | ||
|
a0c06f8d9c | ||
|
814263b442 | ||
|
2f5855a7a4 | ||
|
cd694c01d8 | ||
|
c717f547cf | ||
|
8bffd9725e | ||
|
e25f287d60 | ||
|
9f0bbccd50 | ||
|
29f19d0ab6 | ||
|
58dc7562a3 | ||
|
ee6887b68a | ||
|
2bfbec3e9e | ||
|
705fe11e07 | ||
|
e73ca9e052 | ||
|
6437a5db60 | ||
|
95797b05f1 | ||
|
9939f1cd05 | ||
|
7eb7ad684c | ||
|
826f10239c | ||
|
78bb14bd23 | ||
|
b230c9c639 | ||
|
8cfd843d8f | ||
|
160f1ea8a0 | ||
|
c3ce0eece2 |
26
.github/workflows/linux-cross-appimage-build.yml
vendored
|
@ -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"
|
||||
|
|
4
.github/workflows/main.yml
vendored
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 39 KiB |
1
data/resources/fullscreenui/back-icon.svg
Normal 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 |
BIN
data/resources/fullscreenui/backgrounds/StaticGray.png
Normal file
After Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 34 KiB |
1
data/resources/fullscreenui/desktop-mode.svg
Normal 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 |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 58 KiB |
38
data/resources/fullscreenui/exit.svg
Normal 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 |
Before Width: | Height: | Size: 120 KiB |
46
data/resources/fullscreenui/game-list.svg
Normal 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 |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
1
data/resources/fullscreenui/settings.svg
Normal 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 |
Before Width: | Height: | Size: 35 KiB |
38
data/resources/fullscreenui/start-bios.svg
Normal 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 |
Before Width: | Height: | Size: 24 KiB |
38
data/resources/fullscreenui/start-disc.svg
Normal 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 |
40
data/resources/fullscreenui/start-file.svg
Normal 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 |
|
@ -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"
|
||||
|
|
|
@ -6,3 +6,9 @@ IndentPPDirectives: AfterHash
|
|||
IndentCaseLabels: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
DerivePointerAlignment: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
AlignCaseColons: false
|
|
@ -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)
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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...}});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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...));
|
||||
|
|
|
@ -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};
|
||||
}
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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@" />
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
X(FileLoader) \
|
||||
X(FileSystem) \
|
||||
X(FullscreenUI) \
|
||||
X(GDBProtocol) \
|
||||
X(GDBServer) \
|
||||
X(GPU) \
|
||||
X(GPUDevice) \
|
||||
X(GPUDump) \
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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__
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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")},
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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++;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() &&
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 28;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 29;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|