Compare commits

...

72 Commits

Author SHA1 Message Date
Ty
eee71d54ef Darwin: Implement Mach exceptions ports instead of signals
Credit goes to the Julia language and Dolphin emulator for their free and open source implementations
2026-01-01 13:33:34 -05:00
TheLastRar
43e073a18d GS/DX12: Fix validation errors when depth testing and sampling 2026-01-01 01:20:13 +01:00
JordanTheToaster
bc41666d53 GameDB: Various fixes 2026-01-01 01:09:27 +01:00
PCSX2 Bot
5b85d6a758 [ci skip] PAD: Update to latest controller database. 2025-12-29 17:04:38 +01:00
TheLastRar
1fdc000815 GS/DX12: Don't move to next command list until after wait 2025-12-29 17:04:28 +01:00
TheLastRar
3a57bb46ab GS/DX12: Correct descriptor allocation error messages 2025-12-26 16:20:43 +01:00
TheLastRar
2cb75e60b3 GS/DX12: Free from correct descriptor heap in error handling 2025-12-26 16:20:43 +01:00
TheLastRar
600ac6ec4f GS/DX12: Always require barrier of feedback read 2025-12-24 21:13:31 +01:00
JordanTheToaster
ed0cd628f8 GameDB: Warhammer 40k FW Fixes 2025-12-24 21:12:59 +01:00
JordanTheToaster
33a825c17f GameDB: Guitar Hero Smash Hits fixes 2025-12-23 09:44:52 -05:00
JordanTheToaster
660a165533 3rdparty: Update rcheevos to 12.2.0 2025-12-22 19:10:10 +01:00
TheLastRar
bfe2d5abb2 GS/HW: Split draws before modifying the index buffer 2025-12-21 17:44:21 +01:00
PCSX2 Bot
d5b36da6b0 [ci skip] Qt: Update Base Translation. 2025-12-21 14:35:27 +01:00
PCSX2 Bot
328cebd5fc [ci skip] Qt: Update Base Translation. 2025-12-20 14:07:05 +01:00
lightningterror
579cb7bd27 GS/HW: Use copies if barriers aren't supported properly.
VK/GL: Tex is fb requires the copy to be bound on slot 0 as well.
Fixes validation errors on VK when barriers are force disabled.

DX12: Always create a copy if barriers aren't supported for sw blend or tex is fb.
Fixes issues when barriers are force disabled.
2025-12-20 14:06:40 +01:00
TheLastRar
aab889535f GS/VK: Enable surface maintenance extension if supported 2025-12-20 14:01:21 +01:00
chaoticgd
e0362f7879 FullscreenUI: Fix crash during HDD creation 2025-12-20 14:00:53 +01:00
PCSX2 Bot
009ae1fb02 [ci skip] Qt: Update Base Translation. 2025-12-18 17:52:57 +01:00
chaoticgd
d40289e977 Qt: Add missing include 2025-12-18 08:45:22 -05:00
PCSX2 Bot
c2fd4af163 [ci skip] Qt: Update Base Translation. 2025-12-17 13:12:29 +01:00
SternXD
5bdee3a611 Qt: Add RA Logo to Achievement Login Dialog 2025-12-16 20:19:47 -05:00
TheLastRar
cb026a6946 GS/DX12: Fix handling of stencil DATE one
Co-Authored-By: lightningterror <18107717+lightningterror@users.noreply.github.com>
2025-12-16 16:57:16 +01:00
TheLastRar
cb5124da4b GS/DX12: Enable GBV with the debug device 2025-12-16 16:57:16 +01:00
TheLastRar
7c88af9c73 GS/DX12: Use aliasing resources for feedback 2025-12-16 16:57:16 +01:00
JordanTheToaster
465a31bbd5 GameDB: Juiced post fixes 2025-12-16 13:38:51 +01:00
TheLastRar
6deb43bde2 GS/VK: Support VK_KHR_swapchain_maintenance1
Co-Authored-By: refractionpcsx2 <6278726+refractionpcsx2@users.noreply.github.com>
2025-12-16 12:14:11 +01:00
TheLastRar
8e7dcb83a8 GS/VK: Don't reference old swapchain when recreating on AMD 2025-12-16 12:14:11 +01:00
TheLastRar
51ead1e00f GS/VK: Reduce spam when swapchain needs to be recreated 2025-12-16 12:13:12 +01:00
TheLastRar
27bcb7c29a GS/VK: Don't recreate swapchain during EndPresent 2025-12-16 12:13:12 +01:00
TheLastRar
aaed4a4983 Qt: Don't contain display surface when entering fullscreen/separate on Windows 2025-12-16 12:09:06 +01:00
PCSX2 Bot
748f232976 [ci skip] Qt: Update Base Translation. 2025-12-15 19:50:06 -05:00
chaoticgd
a33612cf7d Qt: Display when the hovered cheat will be applied in the UI 2025-12-15 14:20:58 -05:00
chaoticgd
5c123f3183 Patch: Fix bug causing the UI to show the wrong place value 2025-12-15 14:20:58 -05:00
PCSX2 Bot
d30a7fb991 [ci skip] PAD: Update to latest controller database. 2025-12-15 19:37:04 +01:00
chaoticgd
d4f661c27c SaveState: Swap two OSD error messages that were the wrong way round 2025-12-15 10:40:43 -05:00
dependabot[bot]
6d05d0220d [ci skip] Bump the ci-deps group with 4 updates (#13708)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 08:26:27 -05:00
PCSX2 Bot
7fab935c2d [ci skip] Qt: Update Base Translation. 2025-12-14 19:22:23 -05:00
chaoticgd
cd120c3cfd Patch: Restore original behaviour of PPT_ONCE_ON_LOAD, add new PPT_ON_LOAD_OR_WHEN_ENABLED place option, and display when a patch is applied in the GUI (#13698) 2025-12-14 09:09:53 -05:00
Stern
0180ec060b FSUI/Qt: Add Center/Tile background modes and remove redundant Qt null checks (#13564)
Signed-off-by: SternXD <stern@sidestore.io>
Signed-off-by: SternXD <stern@sidestore.io
Co-authored-by: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
2025-12-14 09:08:34 -05:00
chaoticgd
cf4412ecbe SaveState: Fix error handling in SaveState_ZipToDisk 2025-12-13 21:42:52 -05:00
chaoticgd
743b0b11d8 VMManager: Reword save state error messages 2025-12-13 21:42:52 -05:00
chaoticgd
e8c2cfa843 SaveState: Rework error handling when saving states 2025-12-13 21:42:52 -05:00
chaoticgd
764875ddbf Qt: Add setting to show state load errors using a dialog or OSD message 2025-12-13 21:42:52 -05:00
KamFretoZ
92c7eaa383 FSUI: Add Start Big Picture UI Option 2025-12-13 20:00:17 -05:00
KamFretoZ
4d2c1a82c9 Qt: Misc Shortcut Cleanups 2025-12-13 20:00:17 -05:00
KamFretoZ
eb50aaea35 Qt: Fixup statefile shortcut and icon name
Update ShortcutCreationDialog.cpp
2025-12-13 20:00:17 -05:00
chaoticgd
d69c71e058 Qt: Don't use wildcard disconnection for some settings combo boxes 2025-12-13 14:55:48 +01:00
lightningterror
7cc8c7eee6 GS/DX12: Add debug log when end stencil is discarded. 2025-12-13 14:48:22 +01:00
lightningterror
dd96f2c296 GS/DX12: Preserve end stencil when doing fb copies.
When copying the fb we require the stencil data to be valid so let's preserve it.
2025-12-13 14:48:22 +01:00
JordanTheToaster
3cb2f3d2d9 Deps: Update libpng-apng patch to v1.6.53 2025-12-12 13:31:35 -05:00
JordanTheToaster
1f9a9940e9 Deps: Update libpng to v1.6.53 2025-12-12 13:31:35 -05:00
Ty
8164f2b2db CDVD: Fix an out of bounds read exploit in MG SCMD handlers 2025-12-12 11:39:53 -05:00
Triticum0
d0c54de330 [ci skip] GitHub: Update issue template program version. (#13691) 2025-12-12 16:41:46 +01:00
KamFretoZ
af5cd8d48e Qt/FSUI: Base Translation Update
Co-Authored-By: TellowKrinkle <3315070+TellowKrinkle@users.noreply.github.com>
2025-12-12 16:31:57 +01:00
KamFretoZ
a43f051852 Qt: Add Dedup Comment hint to toolbar buttons 2025-12-12 16:31:57 +01:00
KamFretoZ
0f8c5066e3 Qt: Fix untranslatable strings 2025-12-12 16:31:57 +01:00
KamFretoZ
1bf1f458d0 Qt/Shortcut: Fix missing parenthesis 2025-12-12 16:31:57 +01:00
KamFretoZ
5ef1993496 Qt: Remove unused String 2025-12-12 16:31:57 +01:00
KamFretoZ
80baa73578 Qt/FSUI: Unify VSync String 2025-12-12 16:31:57 +01:00
KamFretoZ
b46c85ee7f FSUI: Translation Strings Fixes 2025-12-12 16:31:57 +01:00
KamFretoZ
7f233ca620 Qt: Translation Fixes From Crowdin
Co-Authored-By: TellowKrinkle <3315070+TellowKrinkle@users.noreply.github.com>
2025-12-12 16:31:57 +01:00
KamFretoZ
47fe2344a5 Qt/FSUI: Few Save State String Fixes 2025-12-12 16:31:57 +01:00
KamFretoZ
4e763d6ff7 Qt/FSUI: Fix FIFA case spelling 2025-12-12 16:31:57 +01:00
KamFretoZ
e1cc994cca Qt: Unify Bilinear Dirty Upscale string 2025-12-12 16:31:57 +01:00
KamFretoZ
5b7f85e571 Qt: Unify Skip Draw String 2025-12-12 16:31:57 +01:00
KamFretoZ
7475cfb325 Qt: Clarify open directory menu 2025-12-12 16:31:57 +01:00
KamFretoZ
cee01a22e1 Qt/Host: Clarify download error message
This is used if it can't write the downloaded data
2025-12-12 16:31:57 +01:00
KamFretoZ
c6437bccad Qt/Debugger: Dont translate symbolSourceErrorMessage 2025-12-12 16:31:57 +01:00
KamFretoZ
7419311296 Qt: Fix DEV9 Host name header title 2025-12-12 16:31:57 +01:00
KamFretoZ
2807a06d76 Qt: Fix audio sync description confusion 2025-12-12 16:31:57 +01:00
refractionpcsx2
14f76c5627 GS/HW: Detect offset shuffle on TexIsFB 2025-12-12 13:27:51 +01:00
PCSX2 Bot
260eaa6c6c [ci skip] Qt: Update Base Translation. 2025-12-12 07:58:41 +01:00
108 changed files with 6489 additions and 4065 deletions

View File

@@ -59,7 +59,7 @@ body:
attributes:
label: PCSX2 Revision
description: "Please ensure you are on the latest version before making an issue"
placeholder: "Example: v1.7.1337"
placeholder: "Example: v2.5.374"
validations:
required: true
- type: dropdown

View File

@@ -76,7 +76,7 @@ body:
attributes:
label: PCSX2 Revision
description: "We only accept bug reports for the latest dev version. Please try upgrading before making an issue."
placeholder: "Example: v1.7.1337"
placeholder: "Example: v2.5.374"
validations:
required: true
- type: dropdown

View File

@@ -17,7 +17,7 @@ jobs:
run: ./.github/workflows/scripts/common/update_base_translation.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: "Qt: Update Base Translation"
commit-message: "[ci skip] Qt: Update Base Translation."

View File

@@ -19,7 +19,7 @@ jobs:
mv ./game_controller_db.txt ${{github.workspace}}/bin/resources/game_controller_db.txt
- name: Create Pull Request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: "PAD: Update to latest controller database"
commit-message: "[ci skip] PAD: Update to latest controller database."

View File

@@ -153,7 +153,7 @@ jobs:
mv "./${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak" "$GITHUB_WORKSPACE"/ci-artifacts/
- name: Upload artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts

View File

@@ -92,7 +92,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: ccache cache files
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .ccache
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} ${{ inputs.detail }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@@ -114,7 +114,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/linux/build-dependencies-qt.sh', '.github/workflows/scripts/common/*.patch') }}
@@ -174,7 +174,7 @@ jobs:
- name: Upload artifact
if: inputs.buildAppImage == true
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts

View File

@@ -91,7 +91,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/deps
key: ${{ inputs.os }} deps ${{ hashFiles('.github/workflows/scripts/macos/*', '.github/workflows/scripts/common/*.patch') }}
@@ -112,7 +112,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: Cache ccache cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .ccache
key: ${{ inputs.os }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@@ -197,7 +197,7 @@ jobs:
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
- name: Upload Artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: "*.tar.xz"

View File

@@ -168,7 +168,7 @@ jobs:
- name: Prepare Artifact Folder
run: mkdir ./ci-artifacts/
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
name: Download all Artifacts
with:
path: ./ci-artifacts/

View File

@@ -23,7 +23,7 @@ FREETYPE=2.14.1
HARFBUZZ=12.2.0
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.2
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBWEBP=1.6.0
NVENC=11.1.5.3
SDL=SDL3-3.2.26
@@ -52,10 +52,10 @@ b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG
f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARFBUZZ.tar.gz
fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.zip
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
2974b91062197e0527dffa3aadd8fe3bfa6681ae45f5ff9181bc0ca6479abd59 nv-codec-headers-$NVENC.tar.gz
c465aa56757e7746ac707f582b6e2d51546569a4a2488c1172fb543aa5fdfc2c vulkan-sdk-$VULKAN.tar.gz

View File

@@ -14,21 +14,21 @@
"sources": [
{
"type": "archive",
"url": "https://downloads.sourceforge.net/project/libpng/libpng16/1.6.51/libpng-1.6.51.tar.xz",
"sha256": "a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2"
"url": "https://downloads.sourceforge.net/project/libpng/libpng16/1.6.53/libpng-1.6.53.tar.xz",
"sha256": "1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4"
},
{
"type": "file",
"url": "https://download.sourceforge.net/libpng-apng/libpng-1.6.51-apng.patch.gz",
"dest-filename": "libpng-1.6.51-apng.patch.gz",
"sha256": "9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023"
"url": "https://download.sourceforge.net/libpng-apng/libpng-1.6.53-apng.patch.gz",
"dest-filename": "libpng-1.6.53-apng.patch.gz",
"sha256": "452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c"
},
{
"type": "shell",
"commands":
[
"gunzip -f libpng-1.6.51-apng.patch.gz",
"patch -p1 < \"libpng-1.6.51-apng.patch\""
"gunzip -f libpng-1.6.53-apng.patch.gz",
"patch -p1 < \"libpng-1.6.53-apng.patch\""
]
}
],

View File

@@ -43,7 +43,7 @@ HARFBUZZ=12.2.0
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
@@ -83,9 +83,9 @@ f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARF
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz

View File

@@ -25,7 +25,7 @@ HARFBUZZ=12.2.0
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
@@ -64,9 +64,9 @@ f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARF
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz

View File

@@ -45,8 +45,8 @@ cd "%BUILDDIR%"
set FREETYPE=2.14.1
set HARFBUZZ=12.2.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1651
set LIBPNGLONG=1.6.51
set LIBPNG=1653
set LIBPNGLONG=1.6.53
set SDL=SDL3-3.2.26
set QT=6.10.1
set QTMINOR=6.10
@@ -67,8 +67,8 @@ set SHADERC_SPIRVTOOLS=971a7b6e8d7740035bbff089bbbf9f42951ecfd5
call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects/freetype/files/freetype2/%FREETYPE%/freetype-%FREETYPE%.tar.gz/download 174d9e53402e1bf9ec7277e22ec199ba3e55a6be2c0740cb18c0ee9850fc8c34 || goto error
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip 31490c781bacd2ce56862555b11c51c964977c39f14f51b817dfaecf0be089fe || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1651.zip 31c2c6505fc1bb613574fd12357684b4e0292650607416ef1e68e6e4e0c470c8 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1653.zip 140566abc64bb2320cb35f1d154d1cb3eb7174a12234d33bfdffb446bdc0a1d2 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error

View File

@@ -43,8 +43,8 @@ cd "%BUILDDIR%"
set FREETYPE=2.14.1
set HARFBUZZ=12.2.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1651
set LIBPNGLONG=1.6.51
set LIBPNG=1653
set LIBPNGLONG=1.6.53
set SDL=SDL3-3.2.26
set QT=6.10.1
set QTMINOR=6.10
@@ -65,8 +65,8 @@ set SHADERC_SPIRVTOOLS=971a7b6e8d7740035bbff089bbbf9f42951ecfd5
call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects/freetype/files/freetype2/%FREETYPE%/freetype-%FREETYPE%.tar.gz/download 174d9e53402e1bf9ec7277e22ec199ba3e55a6be2c0740cb18c0ee9850fc8c34 || goto error
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip 31490c781bacd2ce56862555b11c51c964977c39f14f51b817dfaecf0be089fe || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1651.zip 31c2c6505fc1bb613574fd12357684b4e0292650607416ef1e68e6e4e0c470c8 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1653.zip 140566abc64bb2320cb35f1d154d1cb3eb7174a12234d33bfdffb446bdc0a1d2 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error

View File

@@ -115,7 +115,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/windows/build-dependencies.bat', '.github/workflows/scripts/common/*.patch') }}
@@ -154,7 +154,7 @@ jobs:
cmake --build build --config Release --target unittests
- name: Upload artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: |
@@ -186,7 +186,7 @@ jobs:
}
- name: Upload artifact - with symbols
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
path: |

View File

@@ -1,3 +1,18 @@
# v12.2.0
* add rc_client_create_subset_list
* add rc_client_begin_fetch_game_titles
* greatly improve performance parsing long AddSource chains
* don't send pings if not processing frames; allows server to suspend session while emulator is paused
* modify validation logic to return most severe error instead of first error found
* improve validation warning when 'PauseIf {recall}' attempts to use non-PauseIf Remember
* fix rounding error when subtracting floats from integers
* fix infinite loop processing 'Remember {recall}' with no modifiers
* fix measured value jumping to 0 if all measured-generating alts are paused
* fix buffer overflow converting long user names between rc_client_external versions
* fix validation warning when adding differently sized values
* fix validation warning when ResetIf only applies to hit count inside an AndNext chain
* fix validation warning when only last node of modified memref chain differs
# v12.1.0
* add rc_client_get_user_subset_summary
* add validation warning for using MeasuredIf without Measured

View File

@@ -211,6 +211,8 @@ typedef struct rc_api_game_title_entry_t {
const char* title;
/* The image name for the game badge */
const char* image_name;
/* The URL for the game badge image */
const char* image_url;
}
rc_api_game_title_entry_t;

View File

@@ -364,6 +364,22 @@ RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client
RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary);
typedef struct rc_client_subset_list_t {
const rc_client_subset_t** subsets;
uint32_t num_subsets;
} rc_client_subset_list_t;
/**
* Creates a list of subsets for the currently loaded game.
* Returns an allocated list that must be free'd by calling rc_client_destroy_subset_list.
*/
RC_EXPORT rc_client_subset_list_t* RC_CCONV rc_client_create_subset_list(rc_client_t* client);
/**
* Destroys a list allocated by rc_client_create_subset_list_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_subset_list(rc_client_subset_list_t* list);
/*****************************************************************************\
| Fetch Game Hashes |
\*****************************************************************************/
@@ -398,6 +414,42 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_hash_library(
*/
RC_EXPORT void RC_CCONV rc_client_destroy_hash_library(rc_client_hash_library_t* list);
/*****************************************************************************\
| Fetch Game Titles |
\*****************************************************************************/
typedef struct rc_client_game_title_entry_t {
uint32_t game_id;
const char* title;
char badge_name[16];
const char* badge_url;
} rc_client_game_title_entry_t;
typedef struct rc_client_game_title_list_t {
rc_client_game_title_entry_t* entries;
uint32_t num_entries;
} rc_client_game_title_list_t;
/**
* Callback that is fired when a game titles request completes. list may be null if the query failed.
*/
typedef void(RC_CCONV* rc_client_fetch_game_titles_callback_t)(int result, const char* error_message,
rc_client_game_title_list_t* list, rc_client_t* client,
void* callback_userdata);
/**
* Starts an asynchronous request for titles and badge names for the specified games.
* The caller must provide an array of game IDs and the number of IDs in the array.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles(
rc_client_t* client, const uint32_t* game_ids, uint32_t num_game_ids,
rc_client_fetch_game_titles_callback_t callback, void* callback_userdata);
/**
* Destroys a previously-allocated result from the rc_client_begin_fetch_game_titles() callback.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
@@ -503,7 +555,7 @@ enum {
RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
/**
* Destroys a list allocated by rc_client_get_achievement_list.
* Destroys a list allocated by rc_client_create_achievement_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);

View File

@@ -173,6 +173,8 @@ enum {
RC_OPERATOR_SUB,
RC_OPERATOR_SUB_PARENT, /* internal use */
RC_OPERATOR_ADD_ACCUMULATOR, /* internal use */
RC_OPERATOR_SUB_ACCUMULATOR, /* internal use */
RC_OPERATOR_INDIRECT_READ /* internal use */
};

View File

@@ -448,7 +448,8 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ImageIcon")
RC_JSON_NEW_FIELD("ImageIcon"),
RC_JSON_NEW_FIELD("ImageUrl")
};
memset(response, 0, sizeof(*response));
@@ -482,6 +483,10 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))
return RC_MISSING_VALUE;
rc_json_get_optional_string(&entry->image_url, &response->response, &entry_fields[3], "ImageUrl", "");
if (!entry->image_url[0])
entry->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, entry->image_name);
++entry;
}
}

View File

@@ -1686,6 +1686,67 @@ static void rc_client_free_pending_media(rc_client_pending_media_t* pending_medi
free(pending_media);
}
static void rc_client_log_active_assets(rc_client_t* client)
{
uint32_t num_achievements;
uint32_t num_active_achievements;
uint32_t num_unsupported_achievements;
uint32_t num_leaderboards;
uint32_t num_unsupported_leaderboards;
const rc_client_achievement_info_t* ach;
const rc_client_achievement_info_t* ach_stop;
const rc_client_leaderboard_info_t* lbd;
const rc_client_leaderboard_info_t* lbd_stop;
const rc_client_subset_info_t* subset = client->game->subsets;
for (; subset; subset = subset->next) {
num_achievements = 0;
num_active_achievements = 0;
num_unsupported_achievements = 0;
num_leaderboards = 0;
num_unsupported_leaderboards = 0;
ach = subset->achievements;
ach_stop = ach + subset->public_.num_achievements;
for (; ach < ach_stop; ++ach) {
if (ach->public_.category == RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE) {
++num_achievements;
if (ach->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE)
++num_active_achievements;
else if (ach->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_DISABLED)
++num_unsupported_achievements;
}
}
lbd = subset->leaderboards;
lbd_stop = lbd + subset->public_.num_leaderboards;
for (; lbd < lbd_stop; ++lbd) {
++num_leaderboards;
if (lbd->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED)
++num_unsupported_leaderboards;
}
if (num_unsupported_achievements) {
if (num_unsupported_leaderboards) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active (%u unsupported), %u leaderboards (%u unsupported)",
subset->public_.id, num_active_achievements, num_achievements, num_unsupported_achievements, num_leaderboards, num_unsupported_leaderboards);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active (%u unsupported), %u leaderboards",
subset->public_.id, num_active_achievements, num_achievements, num_unsupported_achievements, num_leaderboards);
}
}
else if (num_unsupported_leaderboards) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active, %u leaderboards (%u unsupported)",
subset->public_.id, num_active_achievements, num_achievements, num_leaderboards, num_unsupported_leaderboards);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active, %u leaderboards",
subset->public_.id, num_active_achievements, num_achievements, num_leaderboards);
}
}
}
/* NOTE: address validation uses the read_memory callback to make sure the client
* will return data for the requested address. As such, this function must
* respect the `client->state.allow_background_memory_reads setting. Use
@@ -1720,10 +1781,13 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
/* make the loaded game active if another game is not aleady being loaded. */
rc_mutex_lock(&client->state.mutex);
if (client->state.load == load_state)
if (client->state.load == load_state) {
client->game = load_state->game;
else
client->state.frames_processed = client->state.frames_at_last_ping = 0;
}
else {
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
}
rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
@@ -1807,6 +1871,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
RC_CLIENT_LOG_INFO_FORMATTED(client, "Game %u loaded, hardcore %s%s", load_state->game->public_.id,
client->state.hardcore ? "enabled" : "disabled",
(client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) ? ", spectating" : "");
if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO)
rc_client_log_active_assets(client);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Subset %u loaded", load_state->subset->public_.id);
@@ -2352,6 +2419,7 @@ static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state
rc_mutex_lock(&client->state.mutex);
client->state.load = load_state;
client->state.frames_processed = client->state.frames_at_last_ping = 0;
rc_mutex_unlock(&client->state.mutex);
}
else if (client->state.load != load_state) {
@@ -3483,6 +3551,58 @@ const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_
return NULL;
}
rc_client_subset_list_t* rc_client_create_subset_list(rc_client_t* client)
{
rc_client_subset_list_info_t* list;
const rc_client_subset_info_t* subset;
const rc_client_subset_t** subset_ptr;
const uint32_t list_size = RC_ALIGN(sizeof(*list));
uint32_t num_subsets = 0;
if (!client)
return (rc_client_subset_list_t*)calloc(1, list_size);
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->create_subset_list)
return (rc_client_subset_list_t*)client->state.external_client->create_subset_list();
#endif
if (!client->game)
return (rc_client_subset_list_t*)calloc(1, list_size);
rc_mutex_lock(&client->state.mutex);
subset = client->game->subsets;
for (; subset; subset = subset->next) {
if (subset->active)
num_subsets++;
}
list = (rc_client_subset_list_info_t*)malloc(list_size + num_subsets * sizeof(rc_client_subset_t*));
list->public_.subsets = subset_ptr = (const rc_client_subset_t**)((uint8_t*)list + list_size);
subset = client->game->subsets;
for (; subset; subset = subset->next) {
if (subset->active)
*subset_ptr++ = &subset->public_;
}
rc_mutex_unlock(&client->state.mutex);
list->destroy_func = NULL;
list->public_.num_subsets = (uint32_t)(subset_ptr - list->public_.subsets);
return &list->public_;
}
void rc_client_destroy_subset_list(rc_client_subset_list_t* list)
{
rc_client_subset_list_info_t* info = (rc_client_subset_list_info_t*)list;
if (info->destroy_func)
info->destroy_func(info);
else
free(list);
}
/* ===== Fetch Game Hashes ===== */
typedef struct rc_client_fetch_hash_library_callback_data_t {
@@ -3595,6 +3715,158 @@ void rc_client_destroy_hash_library(rc_client_hash_library_t* list)
free(list);
}
/* ===== Fetch Game Titles ===== */
typedef struct rc_client_fetch_game_titles_callback_data_t {
rc_client_t* client;
rc_client_fetch_game_titles_callback_t callback;
void* callback_userdata;
rc_client_async_handle_t async_handle;
} rc_client_fetch_game_titles_callback_data_t;
static void rc_client_fetch_game_titles_callback(const rc_api_server_response_t* server_response, void* callback_data)
{
rc_client_fetch_game_titles_callback_data_t* titles_callback_data =
(rc_client_fetch_game_titles_callback_data_t*)callback_data;
rc_client_t* client = titles_callback_data->client;
rc_api_fetch_game_titles_response_t titles_response;
const char* error_message;
int result;
result = rc_client_end_async(client, &titles_callback_data->async_handle);
if (result) {
if (result != RC_CLIENT_ASYNC_DESTROYED)
RC_CLIENT_LOG_VERBOSE(client, "Fetch game titles aborted");
free(titles_callback_data);
return;
}
result = rc_api_process_fetch_game_titles_server_response(&titles_response, server_response);
error_message =
rc_client_server_error_message(&result, server_response->http_status_code, &titles_response.response);
if (error_message) {
RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch game titles failed: %s", error_message);
titles_callback_data->callback(result, error_message, NULL, client, titles_callback_data->callback_userdata);
} else {
rc_client_game_title_list_t* list;
size_t strings_size = 0;
const rc_api_game_title_entry_t* src;
const rc_api_game_title_entry_t* stop;
size_t list_size;
/* calculate string buffer size */
for (src = titles_response.entries, stop = src + titles_response.num_entries; src < stop; ++src) {
if (src->title)
strings_size += strlen(src->title) + 1;
if (src->image_url)
strings_size += strlen(src->image_url) + 1;
}
list_size = sizeof(*list) + sizeof(rc_client_game_title_entry_t) * titles_response.num_entries + strings_size;
list = (rc_client_game_title_list_t*)malloc(list_size);
if (!list) {
titles_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client,
titles_callback_data->callback_userdata);
} else {
rc_client_game_title_entry_t* entry = list->entries =
(rc_client_game_title_entry_t*)((uint8_t*)list + sizeof(*list));
char* strings = (char*)((uint8_t*)list + sizeof(*list) +
sizeof(rc_client_game_title_entry_t) * titles_response.num_entries);
for (src = titles_response.entries, stop = src + titles_response.num_entries; src < stop; ++src, ++entry) {
entry->game_id = src->id;
if (src->title) {
const size_t len = strlen(src->title) + 1;
entry->title = strings;
memcpy(strings, src->title, len);
strings += len;
} else {
entry->title = NULL;
}
if (src->image_name)
snprintf(entry->badge_name, sizeof(entry->badge_name), "%s", src->image_name);
else
entry->badge_name[0] = '\0';
if (src->image_url) {
const size_t len = strlen(src->image_url) + 1;
entry->badge_url = strings;
memcpy(strings, src->image_url, len);
strings += len;
}
else {
entry->badge_url = NULL;
}
}
list->num_entries = titles_response.num_entries;
titles_callback_data->callback(RC_OK, NULL, list, client, titles_callback_data->callback_userdata);
}
}
rc_api_destroy_fetch_game_titles_response(&titles_response);
free(titles_callback_data);
}
rc_client_async_handle_t* rc_client_begin_fetch_game_titles(rc_client_t* client, const uint32_t* game_ids,
uint32_t num_game_ids,
rc_client_fetch_game_titles_callback_t callback,
void* callback_userdata)
{
rc_api_fetch_game_titles_request_t api_params;
rc_client_fetch_game_titles_callback_data_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_request_t request;
int result;
const char* error_message;
if (!client) {
callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata);
return NULL;
}
if (!game_ids || num_game_ids == 0) {
callback(RC_INVALID_STATE, "game_ids is required", NULL, client, callback_userdata);
return NULL;
}
api_params.game_ids = game_ids;
api_params.num_game_ids = num_game_ids;
result = rc_api_init_fetch_game_titles_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
error_message = rc_error_str(result);
callback(result, error_message, NULL, client, callback_userdata);
return NULL;
}
callback_data = (rc_client_fetch_game_titles_callback_data_t*)calloc(1, sizeof(*callback_data));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata);
return NULL;
}
callback_data->client = client;
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_fetch_game_titles_callback, callback_data, client);
rc_api_destroy_request(&request);
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
void rc_client_destroy_game_title_list(rc_client_game_title_list_t* list)
{
free(list);
}
/* ===== Achievements ===== */
static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time)
@@ -5160,30 +5432,37 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
char buffer[256];
int result;
if (!client->callbacks.rich_presence_override ||
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
rc_mutex_lock(&client->state.mutex);
/* if no frames have been processed since the last ping, the emulator is idle. let the
* server session expire. it will be resumed/restarted once frames start getting
* processed again. */
if (client->state.frames_processed != client->state.frames_at_last_ping) {
client->state.frames_at_last_ping = client->state.frames_processed;
rc_runtime_get_richpresence(&client->game->runtime, buffer, sizeof(buffer),
client->state.legacy_peek, client, NULL);
if (!client->callbacks.rich_presence_override ||
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
rc_mutex_lock(&client->state.mutex);
rc_mutex_unlock(&client->state.mutex);
}
rc_runtime_get_richpresence(&client->game->runtime, buffer, sizeof(buffer),
client->state.legacy_peek, client, NULL);
memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;
rc_mutex_unlock(&client->state.mutex);
}
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result));
}
else {
client->callbacks.server_call(&request, rc_client_ping_callback, client, client);
memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result));
}
else {
client->callbacks.server_call(&request, rc_client_ping_callback, client, client);
}
}
callback_data->when = now + 120 * 1000;
@@ -5885,6 +6164,8 @@ void rc_client_do_frame(rc_client_t* client)
rc_mutex_unlock(&client->state.mutex);
rc_client_raise_pending_events(client, client->game);
++client->state.frames_processed;
}
/* we've processed a frame. if there's a pause delay in effect, process it */

View File

@@ -9,13 +9,15 @@
/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
#define RC_CLIENT_IMAGE_URL_BUFFER_SIZE 64
/* https://media.retroachievements.org/UserPic/TwentyCharUserNameXX.png is 69 with null terminator */
#define RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE 80
typedef struct rc_client_external_conversions_t {
rc_client_user_t user;
rc_client_game_t game;
rc_client_subset_t subsets[4];
rc_client_achievement_t achievements[16];
char user_avatar_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char user_avatar_url[RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE];
char game_badge_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char subset_badge_url[4][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char achievement_badge_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
@@ -24,7 +26,7 @@ typedef struct rc_client_external_conversions_t {
uint32_t next_achievement_index;
} rc_client_external_conversions_t;
static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t image_type, const char* image_name)
static const char* rc_client_external_build_avatar_url(char buffer[], size_t buffer_size, uint32_t image_type, const char* image_name)
{
rc_api_fetch_image_request_t image_request;
rc_api_request_t request;
@@ -38,7 +40,7 @@ static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t i
if (result != RC_OK)
return NULL;
strcpy_s(buffer, RC_CLIENT_IMAGE_URL_BUFFER_SIZE, request.url);
snprintf(buffer, buffer_size, "%s", request.url);
return buffer;
}
@@ -69,7 +71,9 @@ const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* cl
RC_CONVERSION_FILL(converted, rc_client_user_t, v1_rc_client_user_t);
converted->avatar_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->user_avatar_url, RC_IMAGE_TYPE_USER, v1_user->username);
client->state.external_client_conversions->user_avatar_url,
sizeof(client->state.external_client_conversions->user_avatar_url),
RC_IMAGE_TYPE_USER, v1_user->username);
return converted;
}
@@ -88,7 +92,9 @@ const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* cl
RC_CONVERSION_FILL(converted, rc_client_game_t, v1_rc_client_game_t);
converted->badge_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->game_badge_url, RC_IMAGE_TYPE_GAME, v1_game->badge_name);
client->state.external_client_conversions->game_badge_url,
sizeof(client->state.external_client_conversions->game_badge_url),
RC_IMAGE_TYPE_GAME, v1_game->badge_name);
return converted;
}
@@ -123,7 +129,9 @@ const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t
memcpy(converted, v1_subset, sizeof(v1_rc_client_subset_t));
RC_CONVERSION_FILL(converted, rc_client_subset_t, v1_rc_client_subset_t);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->subset_badge_url[0]),
RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
return converted;
}
@@ -161,8 +169,12 @@ const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const r
memcpy(converted, v1_achievement, sizeof(v1_rc_client_achievement_t));
RC_CONVERSION_FILL(converted, rc_client_achievement_t, v1_rc_client_achievement_t);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url,
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
return converted;
}
@@ -246,9 +258,13 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con
*achievement = &new_list->achievements[num_achievements++];
memcpy(*achievement, *src_achievement, sizeof(**src_achievement));
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
}
}

View File

@@ -61,6 +61,11 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_lead
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/* NOTE: rc_client_external_create_subset_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_subset_list_info_t;
typedef struct rc_client_subset_list_info_t* (RC_CCONV* rc_client_external_create_subset_list_func_t)();
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer, size_t buffer_size);
@@ -144,6 +149,9 @@ typedef struct rc_client_external_t
rc_client_external_get_user_game_summary_func_t get_user_game_summary_v5;
rc_client_external_get_user_subset_summary_func_t get_user_subset_summary;
/* VERSION 6 */
rc_client_external_create_subset_list_func_t create_subset_list;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 5

View File

@@ -222,6 +222,14 @@ typedef struct rc_client_subset_info_t {
uint8_t pending_events;
} rc_client_subset_info_t;
struct rc_client_subset_list_info_t;
typedef void (RC_CCONV* rc_client_destroy_subset_list_func_t)(struct rc_client_subset_list_info_t* list);
typedef struct rc_client_subset_list_info_t {
rc_client_subset_list_t public_;
rc_client_destroy_subset_list_func_t destroy_func;
} rc_client_subset_list_info_t;
/*****************************************************************************\
| Game |
\*****************************************************************************/
@@ -316,6 +324,8 @@ typedef struct rc_client_state_t {
rc_client_raintegration_t* raintegration;
#endif
uint32_t frames_processed;
uint32_t frames_at_last_ping;
uint16_t unpaused_frame_decay;
uint16_t required_unpaused_frames;

View File

@@ -649,6 +649,7 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
uint32_t i, j;
rc_libretro_core_memory_info_t info;
size_t offset;
int found_aligning_padding = 0;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
@@ -656,6 +657,16 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
const uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
uint32_t base_address = 0;
if (console_region->type == RC_MEMORY_TYPE_UNUSED && console_region_size >= 0x10000 && !found_aligning_padding) {
if (console_regions->region[console_regions->num_regions - 1].end_address > 0x01000000) {
/* assume anything exposing more than 16MB of regions with at least one 64KB+ UNUSED region
* is padding so things align with real addresses. this indicates the memory is disjoint
* in the system, so we cannot expect it to be contiguous in the RETRO_SYSTEM_RAM.
* stop processing regions now, and just fill the remaining memory map with null filler. */
found_aligning_padding = 1;
}
}
for (j = 0; j <= i; ++j) {
const rc_memory_region_t* console_region2 = &console_regions->region[j];
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
@@ -665,7 +676,13 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
}
offset = console_region->start_address - base_address;
get_core_memory_info(type, &info);
if (!found_aligning_padding) {
get_core_memory_info(type, &info);
}
else {
info.data = NULL;
info.size = console_region_size;
}
if (offset < info.size) {
info.size -= offset;

View File

@@ -8,7 +8,7 @@
RC_BEGIN_C_DECLS
#define RCHEEVOS_VERSION_MAJOR 12
#define RCHEEVOS_VERSION_MINOR 1
#define RCHEEVOS_VERSION_MINOR 2
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)

View File

@@ -374,7 +374,7 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
}
parse->addsource_oper = RC_OPERATOR_ADD;
parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR;
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
@@ -388,13 +388,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
/* type determined by parent */
const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS;
if (parse->addsource_oper == RC_OPERATOR_ADD && !rc_operand_is_memref(&parse->addsource_parent)) {
if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR && !rc_operand_is_memref(&parse->addsource_parent)) {
/* if the previous element was a constant we have to turn it into a memref by adding zero */
rc_modified_memref_t* memref;
rc_operand_t zero;
rc_operand_set_const(&zero, 0);
memref = rc_alloc_modified_memref(parse,
parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD, &zero);
parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD_ACCUMULATOR, &zero);
parse->addsource_parent.value.memref = (rc_memref_t*)memref;
parse->addsource_parent.type = RC_OPERAND_ADDRESS;
}
@@ -414,19 +414,27 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
}
/* subtract the condition from the chain */
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB : RC_OPERATOR_SUB_PARENT;
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT;
rc_condition_convert_to_operand(condition, &cond_operand, parse);
rc_operand_addsource(&cond_operand, parse, new_size);
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
/* indicate the next value can be added to the chain */
parse->addsource_oper = RC_OPERATOR_ADD;
parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR;
}
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
case RC_CONDITION_REMEMBER:
if (condition->operand1.type == RC_OPERAND_RECALL &&
condition->oper == RC_OPERATOR_NONE &&
parse->addsource_parent.type == RC_OPERAND_NONE &&
parse->indirect_parent.type == RC_OPERAND_NONE) {
/* Remembering {recall} without any modifications is a no-op */
break;
}
rc_condition_convert_to_operand(condition, &condition->operand1, parse);
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
@@ -465,6 +473,9 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
default:
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR)
parse->addsource_oper = RC_OPERATOR_ADD;
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
condition->operand1.is_combining = 1;

View File

@@ -62,8 +62,10 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, c
do {
rc_parse_condition_internal(&condition, &memaddr, &parse);
if (parse.offset < 0)
if (parse.offset < 0) {
rc_destroy_parse_state(&parse);
return parse.offset;
}
++index;
@@ -106,7 +108,9 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, c
* logic in rc_find_next_classification */
self->num_other_conditions += chain_length - 1;
return index;
rc_destroy_parse_state(&parse);
return (int32_t)index;
}
static int rc_find_next_classification(const char* memaddr) {

View File

@@ -154,8 +154,14 @@ rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t
memcpy(&modified_memref->parent, parent, sizeof(modified_memref->parent));
memcpy(&modified_memref->modifier, modifier, sizeof(modified_memref->modifier));
modified_memref->modifier_type = modifier_type;
modified_memref->depth = 0;
modified_memref->memref.address = rc_operand_is_memref(modifier) ? modifier->value.memref->address : modifier->value.num;
if (rc_operand_is_memref(parent) && parent->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_t* parent_modified_memref = (rc_modified_memref_t*)parent->value.memref;
modified_memref->depth = parent_modified_memref->depth + 1;
}
return modified_memref;
}
@@ -729,11 +735,34 @@ uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_pee
break;
case RC_OPERATOR_SUB_PARENT:
/* sub parent is "-parent + modifier" */
rc_typed_value_negate(&value);
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
case RC_OPERATOR_SUB_ACCUMULATOR:
rc_typed_value_negate(&modifier);
/* fallthrough */ /* to case RC_OPERATOR_SUB_ACCUMULATOR */
case RC_OPERATOR_ADD_ACCUMULATOR:
/* when modifying the accumulator, force the modifier to match the accumulator
* type instead of promoting them both to the less restrictive type.
*
* 18 - 17.5 will result in an integer. should it be 0 or 1?
*
* default: float is less restrictive, convert both to float for combine,
* then convert to the memref type.
* (int)((float)18 - 17.5) -> (int)(0.5) -> 0
*
* accumulator is integer: force modifier to be integer before combining
* (int)(18 - (int)17.5) -> (int)(18 - 17) -> 1
*/
rc_typed_value_convert(&modifier, value.type);
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
default:
rc_typed_value_combine(&value, &modifier, memref->modifier_type);
rc_typed_value_convert(&value, memref->memref.value.type);

View File

@@ -334,8 +334,11 @@ int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) {
const rc_modified_memref_t* left_memref = (const rc_modified_memref_t*)left->value.memref;
const rc_modified_memref_t* right_memref = (const rc_modified_memref_t*)right->value.memref;
return (left_memref->modifier_type == right_memref->modifier_type &&
left_memref->depth == right_memref->depth &&
rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier) &&
rc_operands_are_equal(&left_memref->parent, &right_memref->parent) &&
rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier));
1 == 1
);
}
default:

View File

@@ -14,10 +14,11 @@ typedef struct rc_scratch_string {
rc_scratch_string_t;
typedef struct rc_modified_memref_t {
rc_memref_t memref; /* for compatibility with rc_operand_t.value.memref */
rc_memref_t memref; /* For compatibility with rc_operand_t.value.memref */
rc_operand_t parent; /* The parent memref this memref is derived from (type will always be a memref type) */
rc_operand_t modifier; /* The modifier to apply to the parent. */
uint8_t modifier_type; /* How to apply the modifier to the parent. (RC_OPERATOR_*) */
uint16_t depth; /* The number of parents this memref has. */
}
rc_modified_memref_t;
@@ -382,7 +383,6 @@ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self);
void rc_reset_richpresence_triggers(rc_richpresence_t* self);
void rc_update_richpresence_internal(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud);
int rc_validate_memrefs(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_memrefs_for_console(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t console_id);
RC_END_C_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -235,8 +235,14 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, void* unus
is_paused |= sub_paused;
}
/* if paused, the measured value may not be captured, keep the old value */
if (!is_paused) {
if (is_paused) {
/* if the trigger is fully paused, ignore any updates to the measured value */
}
else if (measured_value.type == RC_VALUE_TYPE_NONE) {
/* if a measured value was not captured, keep the old value (it's possible to pause
* an alt that is generating the measured value without fully pausing the trigger) */
}
else {
rc_typed_value_convert(&measured_value, RC_VALUE_TYPE_UNSIGNED);
self->measured_value = measured_value.value.u32;
}

View File

@@ -67,6 +67,7 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
next_clause = &self->conditions;
do {
/* count the number of joiners and add one to determine the number of clauses. */
buffer[0] = 'A'; /* reset to AddSource */
done = 0;
num_measured_conditions = 1;
buffer_ptr = *memaddr;
@@ -97,8 +98,8 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
}
} while (!done);
/* if last condition is SubSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] == 'B')
/* if last condition is not AddSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] != 'A')
++num_measured_conditions;
condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t,
@@ -121,10 +122,18 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
for (;; ++(*memaddr)) {
switch (**memaddr) {
case '_': /* add next */
*ptr = '\0';
break;
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
/* the last condition needs to be Measured - AddSource can be changed here,
* SubSource will be handled later */
if (buffer[0] == 'A')
buffer[0] = 'M';
*ptr = '\0';
break;
@@ -176,33 +185,34 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
return;
}
rc_condition_update_parse_state(cond, parse);
*next = cond;
next = &cond->next;
if (**memaddr != '_') /* add next */
break;
rc_condition_update_parse_state(cond, parse);
++cond;
}
/* end of clause */
if (cond->type == RC_CONDITION_SUB_SOURCE) {
/* cannot change SubSource to Measured. add a dummy condition */
rc_condition_update_parse_state(cond, parse);
if (parse->buffer)
/* -- end of clause -- */
/* clause must end in a Measured. if it doesn't, append one */
if (cond->type != RC_CONDITION_MEASURED) {
if (!parse->buffer)
cond = &local_cond;
else
++cond;
buffer_ptr = "A:0";
buffer_ptr = "M:0";
rc_parse_condition_internal(cond, &buffer_ptr, parse);
*next = cond;
next = &cond->next;
rc_condition_update_parse_state(cond, parse);
}
/* convert final AddSource condition to Measured */
cond->type = RC_CONDITION_MEASURED;
cond->next = NULL;
rc_condition_update_parse_state(cond, parse);
*next = NULL;
/* finalize clause */
*next_clause = condset;

View File

@@ -1300,7 +1300,18 @@ static void rc_hash_initialize_iterator_from_path(rc_hash_iterator_t* iterator,
bsearch(&search, handlers, num_handlers, sizeof(*handler), rc_hash_iterator_find_handler);
if (handler) {
handler->handler(iterator, handler->data);
} else {
if (iterator->callbacks.verbose_message) {
int count = 0;
while (iterator->consoles[count])
++count;
rc_hash_iterator_verbose_formatted(iterator, "Found %d potential consoles for %s file extension", count, ext);
}
}
else {
rc_hash_iterator_error_formatted(iterator, "No console mapping specified for %s file extension - trying full file hash", ext);
/* if we didn't match the extension, default to something that does a whole file hash */
if (!iterator->consoles[0])
iterator->consoles[0] = RC_CONSOLE_GAMEBOY;
@@ -1332,15 +1343,6 @@ int rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator) {
if (iterator->index == -1) {
rc_hash_initialize_iterator_from_path(iterator, iterator->path);
if (iterator->callbacks.verbose_message) {
int count = 0;
while (iterator->consoles[count])
++count;
rc_hash_iterator_verbose_formatted(iterator, "Found %d potential consoles for %s file extension", count, rc_path_get_extension(iterator->path));
}
iterator->index = 0;
}

View File

@@ -13722,6 +13722,8 @@ SLED-51676:
region: "PAL"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLED-51807:
name: "Summer Heat Beach Volleyball"
region: "PAL-Unk"
@@ -13996,6 +13998,7 @@ SLED-53109:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLED-53137:
name: "Stolen [Demo]"
@@ -16671,6 +16674,8 @@ SLES-50958:
region: "PAL-M4"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLES-50963:
name: "Riding Spirits"
region: "PAL-M5"
@@ -17825,6 +17830,8 @@ SLES-51393:
SLES-51397:
name: "IndyCar Series"
region: "PAL-M5"
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-51398:
name: "World Championship Snooker 2003"
region: "PAL-E"
@@ -19948,6 +19955,8 @@ SLES-52298:
name: "IndyCar Series 2005"
region: "PAL-M5"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-52308:
name: "Karaoke Stage"
region: "PAL-M5"
@@ -20118,6 +20127,9 @@ SLES-52378:
name: "Euro Rally Champion"
region: "PAL-M5"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
textureInsideRT: 1 # Fixes broken fog rendering.
SLES-52379:
name: "Shrek 2"
region: "PAL-E"
@@ -22269,6 +22281,7 @@ SLES-53044:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLES-53045:
name: "Street Racing Syndicate"
@@ -22592,6 +22605,7 @@ SLES-53151:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLES-53152:
name: "Mashed Fully Loaded"
@@ -25050,6 +25064,8 @@ SLES-53957:
SLES-53958:
name: "Noble Racing"
region: "PAL-E"
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-53959:
name: "Pac-Man World 3"
region: "PAL-M5"
@@ -30127,6 +30143,11 @@ SLES-55544:
region: "PAL-M5"
roundModes:
vu1RoundMode: 0 # Fixes VU size spam and potential graphical issues with GH3 engine.
gsHWFixes:
cpuCLUTRender: 1 # Fixes broken rainbow rendering.
halfPixelOffset: 4 # Mostly aligns post-processing.
nativeScaling: 1 # Fixes post-processing smoothness and position.
autoFlush: 1 # Fixes edge garbage and shadow definition.
SLES-55545:
name: "WWE SmackDown! vs. Raw 2010"
region: "PAL-M5"
@@ -32394,6 +32415,7 @@ SLKA-25283:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLKA-25284:
name: "사쿠라 대전 3 ~파리는 불타고 있는가~"
@@ -48603,6 +48625,7 @@ SLPM-66277:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLPM-66278:
name: "新・豪血寺一族 -煩悩解放-"
@@ -66745,6 +66768,8 @@ SLUS-20597:
compat: 5
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLUS-20598:
name: "Everblue 2"
region: "NTSC-U"
@@ -66968,6 +66993,8 @@ SLUS-20641:
name: "IndyCar Series"
region: "NTSC-U"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLUS-20642:
name: "Auto Modellista"
region: "NTSC-U"
@@ -68225,6 +68252,7 @@ SLUS-20872:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLUS-20873:
name: "Silent Hill 4 - The Room"
@@ -74172,8 +74200,10 @@ SLUS-21866:
roundModes:
vu1RoundMode: 0 # Fixes VU size spam and potential graphical issues with GH3 engine.
gsHWFixes:
cpuCLUTRender: 1 # Fixes broken rainbow rendering.
halfPixelOffset: 4 # Mostly aligns post-processing.
nativeScaling: 1 # Fixes post-processing smoothness and position.
autoFlush: 1 # Fixes edge garbage and shadow definition.
SLUS-21867:
name: "Guitar Hero - Van Halen"
region: "NTSC-U"
@@ -74971,6 +75001,8 @@ SLUS-29049:
region: "NTSC-U"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLUS-29050:
name: "EverQuest Online Adventures [Regular Demo]"
region: "NTSC-U"
@@ -75330,6 +75362,7 @@ SLUS-29147:
cpuSpriteRenderLevel: 2 # Needed for above.
autoFlush: 1 # Fixes headlight brightness.
cpuCLUTRender: 1 # Fixes broken headlights.
nativeScaling: 4 # Aligns post effects.
minimumBlendingLevel: 3
SLUS-29148:
name: "The Incredible Hulk - Ultimate Destruction [Demo]"

View File

@@ -78,7 +78,7 @@
03000000c82d00001230000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001260000000000000,8BitDo Ultimate 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001b30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,x:b3,y:b4,back:b10,guide:b12,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,paddle1:b2,paddle2:b5,platform:Windows,
03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001d30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001530000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001630000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
@@ -850,6 +850,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
# Mac OS X
030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000000020000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
@@ -895,7 +896,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000260000001000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
@@ -985,6 +986,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000242f00002d00000007010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000006d04000019c2000000000000,Logitech Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
@@ -993,7 +995,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
@@ -1395,6 +1396,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
03000000d80400004bea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
03000000d80400004aea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
030000008a2e0000d910000011010000,icedragon.io STAC2 Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
030000008a2e0000e910000011010000,icedragon.io STAC2 Dance Pad,a:b8,b:b9,x:b10,y:b11,back:b12,platform:Linux,
030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux,
050000006964726f69643a636f6e0000,idroidcon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
@@ -1591,6 +1596,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
03000000120c0000160e000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
@@ -1701,7 +1707,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000952e00004e43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,
03000000a30c00002500000011010000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Linux,
03000000790000001100000011010000,Sega Saturn,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,Sega Saturn,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
03000000b40400000a01000000010000,Sega Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux,
03000000632500002305000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000632500002605000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@@ -1858,4 +1863,5 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000120c0000100e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000101e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000182e000011010000,Zeroplus PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,ZhiXu GuliKit D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,

View File

@@ -11,6 +11,7 @@
#include "common/Threading.h"
#include "common/WindowInfo.h"
#include "common/HostSys.h"
#include "fmt/format.h"
#include <csignal>
#include <cstring>
@@ -19,12 +20,15 @@
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <thread>
#include <time.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/mach_time.h>
#include <mach/mach_vm.h>
#include <mach/message.h>
#include <mach/task.h>
#include <mach/thread_state.h>
#include <mach/vm_map.h>
#include <mutex>
#include <ApplicationServices/ApplicationServices.h>
@@ -294,7 +298,7 @@ static CPUInfo CalcCPUInfo()
std::vector<DarwinMisc::CPUClass> classes = DarwinMisc::GetCPUClasses();
out.num_clusters = static_cast<u32>(classes.size());
out.num_big_cores = classes.empty() ? 0 : classes[0].num_physical;
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
out.num_small_cores = 0;
for (std::size_t i = 1; i < classes.size(); i++)
{
@@ -568,15 +572,206 @@ void HostSys::EndCodeWrite()
#endif // _M_ARM64
#define USE_MACH_EXCEPTION_PORTS
namespace PageFaultHandler
{
#ifdef USE_MACH_EXCEPTION_PORTS
static void SignalHandler(mach_port_t port);
#else
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
#endif
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
#ifdef USE_MACH_EXCEPTION_PORTS
#if defined(_M_X86)
#define THREAD_STATE64_COUNT x86_THREAD_STATE64_COUNT
#define THREAD_STATE64 x86_THREAD_STATE64
#define thread_state64_t x86_thread_state64_t
#elif defined(_M_ARM64)
#define THREAD_STATE64_COUNT ARM_THREAD_STATE64_COUNT
#define THREAD_STATE64 ARM_THREAD_STATE64
#define thread_state64_t arm_thread_state64_t
#else
#error Unknown Darwin Platform
#endif
void PageFaultHandler::SignalHandler(mach_port_t port)
{
Threading::SetNameOfCurrentThread("Mach Exception Thread");
#pragma pack(4)
struct
{
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[THREAD_STATE64_COUNT];
mach_msg_trailer_t trailer;
} msg_in;
struct
{
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[THREAD_STATE64_COUNT];
} msg_out;
#pragma pack()
memset(&msg_in, 0xee, sizeof(msg_in));
memset(&msg_out, 0xee, sizeof(msg_out));
mach_msg_size_t send_size = 0;
mach_msg_option_t option = MACH_RCV_MSG;
while (true)
{
kern_return_t r;
if ((r = mach_msg_overwrite(&msg_out.Head, option, send_size, sizeof(msg_in), port,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg_in.Head, 0)))
{
pxFail(fmt::format("CRITICAL: mach_msg_overwrite: {:x}", r).c_str());
}
if (msg_in.Head.msgh_id == MACH_NOTIFY_NO_SENDERS)
{
// the other thread exited
mach_port_deallocate(mach_task_self(), port);
return;
}
if (msg_in.Head.msgh_id != 2406)
{
pxFailRel("unknown message received");
return;
}
if (msg_in.flavor != THREAD_STATE64)
{
pxFailRel(fmt::format("unknown flavour {}, expected {}", msg_in.flavor, THREAD_STATE64).c_str());
return;
}
s_exception_handler_mutex.lock();
thread_state64_t* state = (thread_state64_t*)msg_in.old_state;
HandlerResult result = HandlerResult::ExecuteNextHandler;
if (!s_in_exception_handler)
{
s_in_exception_handler = true;
#ifdef _M_ARM64
result = HandlePageFault(reinterpret_cast<void*>(state->__pc), reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
#else
result = HandlePageFault(reinterpret_cast<void*>(state->__rip), reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
#endif
s_in_exception_handler = false;
}
// Set up the reply.
msg_out.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg_in.Head.msgh_bits), 0);
msg_out.Head.msgh_remote_port = msg_in.Head.msgh_remote_port;
msg_out.Head.msgh_local_port = MACH_PORT_NULL;
msg_out.Head.msgh_id = msg_in.Head.msgh_id + 100;
msg_out.NDR = msg_in.NDR;
if (result != HandlerResult::ContinueExecution) // cooked
{
msg_out.RetCode = KERN_FAILURE;
msg_out.flavor = 0;
msg_out.new_stateCnt = 0;
// The crash handler on macOS or Linux doesn't use context passed to it
// Stubbing it here is fine
CrashHandler::CrashSignalHandler(-1, nullptr, nullptr);
pxFailRel("CrashSignalHandler returned when it should have terminated us!");
}
else
{
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV)
msg_out.RetCode = KERN_SUCCESS;
msg_out.flavor = THREAD_STATE64;
msg_out.new_stateCnt = THREAD_STATE64_COUNT;
memcpy(msg_out.new_state, msg_in.old_state, THREAD_STATE64_COUNT * sizeof(natural_t));
}
msg_out.Head.msgh_size =
offsetof(__typeof__(msg_out), new_state) + msg_out.new_stateCnt * sizeof(natural_t);
send_size = msg_out.Head.msgh_size;
option |= MACH_SEND_MSG;
s_exception_handler_mutex.unlock();
}
}
bool PageFaultHandler::Install(Error* error)
{
exception_mask_t masks[EXC_TYPES_COUNT];
mach_port_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
mach_msg_type_number_t count = EXC_TYPES_COUNT;
kern_return_t r = task_get_exception_ports(mach_task_self(), EXC_MASK_ALL,
masks, &count, ports, behaviors, flavors);
mach_port_t port;
if ((r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)))
{
pxFailRel(fmt::format("mach_port_allocate: {:x}", r).c_str());
return false;
}
std::thread sig_thread(PageFaultHandler::SignalHandler, port);
sig_thread.detach();
if ((r = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_insert_right: {:x}", r).c_str());
return false;
}
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
if ((r = thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, THREAD_STATE64)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("thread_set_exception_ports: {:x}", r).c_str());
return false;
}
if ((r = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
return false;
}
mach_port_t previous;
if ((r = mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS, 0, port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
return false;
}
s_installed = true;
return true;
}
#else
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
{
#if defined(_M_X86)
@@ -644,3 +839,4 @@ bool PageFaultHandler::Install(Error* error)
s_installed = true;
return true;
}
#endif

View File

@@ -28,7 +28,7 @@
#endif
#if defined(_WIN32)
#include "RedtapeWindows.h"
#include "common/RedtapeWindows.h"
#include <io.h>
#include <malloc.h>
#include <pathcch.h>

View File

@@ -1148,7 +1148,7 @@ bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
return false;
}
if (width > 65536 || height > 65536)
if (width >= 65536 || height >= 65536)
{
Console.Error("BMP dimensions too large: %ux%u", width, height);
return false;

View File

@@ -272,7 +272,7 @@ if (NOT APPLE)
)
endif()
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/*.ts)
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/pcsx2-qt_*-*.ts)
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)

View File

@@ -133,7 +133,7 @@ void DisplaySurface::handleCloseEvent(QCloseEvent* event)
// In the latter case, it's going to destroy us, so don't let Qt do it first.
// Treat a close event while fullscreen as an exit, that way ALT+F4 closes PCSX2,
// rather than just the game.
if (QtHost::IsVMValid() && !isActuallyFullscreen())
if (QtHost::IsVMValid() && !isFullScreen())
{
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true),
Q_ARG(bool, true), Q_ARG(bool, false));
@@ -147,10 +147,44 @@ void DisplaySurface::handleCloseEvent(QCloseEvent* event)
event->ignore();
}
bool DisplaySurface::isActuallyFullscreen() const
bool DisplaySurface::isFullScreen() const
{
// DisplaySurface is always in a container, so we need to check parent window
return parent()->windowState() & Qt::WindowFullScreen;
// DisplaySurface may be in a container
return (parent() ? parent()->windowState() : windowState()) & Qt::WindowFullScreen;
}
void DisplaySurface::setFocus()
{
if (m_container)
m_container->setFocus();
else
requestActivate();
}
QByteArray DisplaySurface::saveGeometry() const
{
if (m_container)
return m_container->saveGeometry();
else
{
// QWindow lacks saveGeometry, so create a dummy widget and copy geometry across.
QWidget dummy = QWidget();
dummy.setGeometry(geometry());
return dummy.saveGeometry();
}
}
void DisplaySurface::restoreGeometry(const QByteArray& geometry)
{
if (m_container)
m_container->restoreGeometry(geometry);
else
{
// QWindow lacks restoreGeometry, so create a dummy widget and copy geometry across.
QWidget dummy = QWidget();
dummy.restoreGeometry(geometry);
setGeometry(dummy.geometry());
}
}
void DisplaySurface::updateCenterPos()
@@ -393,10 +427,18 @@ bool DisplaySurface::event(QEvent* event)
return event->isAccepted();
case QEvent::Move:
{
updateCenterPos();
return true;
}
// These events only work on the top level control.
// Which is this container when render to seperate or fullscreen is active (Windows).
case QEvent::Close:
handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
case QEvent::WindowStateChange:
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit windowRestoredEvent();
return false;
default:
return QWindow::event(event);
@@ -418,7 +460,7 @@ bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
return true;
// These events only work on the top level control.
// Which is this container when render to seperate or fullscreen is active.
// Which is this container when render to seperate or fullscreen is active (Non-Windows).
case QEvent::Close:
handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
@@ -427,8 +469,8 @@ bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
emit windowRestoredEvent();
return false;
case QEvent::ChildRemoved:
if (static_cast<QChildEvent*>(event)->child() == m_container)
case QEvent::ChildWindowRemoved:
if (static_cast<QChildWindowEvent*>(event)->child() == this)
{
object->removeEventFilter(this);
m_container = nullptr;

View File

@@ -30,6 +30,12 @@ public:
void updateRelativeMode(bool enabled);
void updateCursor(bool hidden);
bool isFullScreen() const;
void setFocus();
QByteArray saveGeometry() const;
void restoreGeometry(const QByteArray& geometry);
Q_SIGNALS:
void windowResizedEvent(int width, int height, float scale);
void windowRestoredEvent();
@@ -47,7 +53,6 @@ private Q_SLOTS:
void onResizeDebounceTimer();
private:
bool isActuallyFullscreen() const;
void updateCenterPos();
QPoint m_relative_mouse_start_pos{};

View File

@@ -220,6 +220,8 @@ void GameListWidget::initialize()
m_sort_model->setSourceModel(m_model);
m_ui.setupUi(this);
m_ui.stack->installEventFilter(this);
m_ui.stack->setAutoFillBackground(false);
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
{
@@ -353,6 +355,7 @@ void GameListWidget::setCustomBackground()
m_background_movie = nullptr;
}
// Get the path to the custom background
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
if (!Path::IsAbsolute(path))
path = Path::Combine(EmuFolders::DataRoot, path);
@@ -360,27 +363,26 @@ void GameListWidget::setCustomBackground()
// Only try to create background if path are valid
if (!path.empty() && FileSystem::FileExists(path.c_str()))
{
QMovie* new_movie;
QString img_path = QString::fromStdString(path);
if (img_path.endsWith(".png", Qt::CaseInsensitive))
// Use apng plugin
new_movie = new QMovie(img_path, "apng", this);
else
new_movie = new QMovie(img_path, QByteArray(), this);
if (new_movie->isValid())
m_background_movie = new_movie;
else
const QByteArray format = (img_path.endsWith(".png", Qt::CaseInsensitive)) ? QByteArray("apng") : QByteArray();
m_background_movie = new QMovie(img_path, format, this);
if (!m_background_movie->isValid())
{
Console.Warning("Failed to load background movie from: %s", path.c_str());
delete new_movie;
delete m_background_movie;
m_background_movie = nullptr;
}
}
// If there is no valid background then reset fallback to default UI state
if (!m_background_movie)
{
m_ui.stack->setPalette(QApplication::palette());
m_background_pixmap = QPixmap();
m_ui.stack->setAutoFillBackground(true);
m_table_view->viewport()->setAutoFillBackground(true);
m_list_view->viewport()->setAutoFillBackground(true);
m_ui.stack->update();
m_table_view->setAlternatingRowColors(true);
return;
}
@@ -390,7 +392,7 @@ void GameListWidget::setCustomBackground()
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++)
{
if (!(InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] == nullptr))
if (InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] != nullptr)
{
if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i])
{
@@ -405,8 +407,13 @@ void GameListWidget::setCustomBackground()
// Selected Custom background is valid, connect the signals and start animation in gamelist
connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection);
m_ui.stack->setAutoFillBackground(false);
m_table_view->viewport()->setAutoFillBackground(false);
m_list_view->viewport()->setAutoFillBackground(false);
updateCustomBackgroundState(true);
m_table_view->setAlternatingRowColors(false);
processBackgroundFrames();
}
void GameListWidget::updateCustomBackgroundState(const bool force_start)
@@ -422,7 +429,7 @@ void GameListWidget::updateCustomBackgroundState(const bool force_start)
void GameListWidget::processBackgroundFrames()
{
if (m_background_movie && m_background_movie->isValid())
if (m_background_movie && m_background_movie->isValid() && isVisible())
{
const int widget_width = m_ui.stack->width();
const int widget_height = m_ui.stack->height();
@@ -435,9 +442,8 @@ void GameListWidget::processBackgroundFrames()
QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity);
QPalette bg_palette(m_ui.stack->palette());
bg_palette.setBrush(QPalette::Base, pm);
m_ui.stack->setPalette(bg_palette);
m_background_pixmap = std::move(pm);
m_ui.stack->update();
}
}
@@ -725,6 +731,7 @@ bool GameListWidget::event(QEvent* event)
if (event->type() == QEvent::DevicePixelRatioChange)
{
m_model->setDevicePixelRatio(devicePixelRatioF());
processBackgroundFrames();
QWidget::event(event);
return true;
}
@@ -732,6 +739,25 @@ bool GameListWidget::event(QEvent* event)
return QWidget::event(event);
}
bool GameListWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_ui.stack && event->type() == QEvent::Paint)
{
if (!m_background_pixmap.isNull())
{
QPainter painter(m_ui.stack);
const auto* paint_event = static_cast<QPaintEvent*>(event);
painter.save();
painter.setClipRect(paint_event->rect());
painter.drawTiledPixmap(m_ui.stack->rect(), m_background_pixmap);
painter.restore();
return true;
}
}
return QWidget::eventFilter(watched, event);
}
void GameListWidget::resizeTableViewColumnsToFit()
{
QtUtils::ResizeColumnsForTableView(m_table_view, {

View File

@@ -10,7 +10,9 @@
#include "pcsx2/GameList.h"
#include <QtGui/QMovie>
#include <QtGui/QPixmap>
#include <QtWidgets/QListView>
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QTableView>
Q_DECLARE_METATYPE(const GameList::Entry*);
@@ -101,6 +103,7 @@ protected:
void hideEvent(QHideEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
bool event(QEvent* event) override;
bool eventFilter(QObject* watched, QEvent* event) override;
private:
void loadTableHeaderState();
@@ -123,6 +126,7 @@ private:
GameListRefreshThread* m_refresh_thread = nullptr;
QMovie* m_background_movie = nullptr;
QPixmap m_background_pixmap;
QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit;
float m_background_opacity = 100.0f;
};

View File

@@ -37,6 +37,7 @@
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/Recording/InputRecording.h"
#include "pcsx2/Recording/InputRecordingControls.h"
#include "pcsx2/SaveState.h"
#include "pcsx2/SIO/Sio.h"
#include "pcsx2/GS/GSExtra.h"
@@ -99,6 +100,13 @@ static quint32 s_current_running_crc;
static bool s_record_on_start = false;
static QString s_path_to_recording_for_record_on_start;
// DX cannot fullscreen when the display surface is in a container.
// QWindow, however, seems to lack CSD under wayland, so needs the container.
// MAC is unknown
#ifdef _WIN32
#define DISPLAY_SURFACE_WINDOW
#endif
MainWindow::MainWindow()
{
pxAssert(!g_main_window);
@@ -1007,10 +1015,15 @@ void MainWindow::updateWindowTitle()
if (windowTitle() != main_title)
setWindowTitle(main_title);
if (m_display_container && !isRenderingToMain())
if (m_display_surface && !isRenderingToMain())
{
#ifdef DISPLAY_SURFACE_WINDOW
if (m_display_surface->title() != display_title)
m_display_surface->setTitle(display_title);
#else
if (m_display_container->windowTitle() != display_title)
m_display_container->setWindowTitle(display_title);
#endif
}
if (g_log_window)
@@ -1039,7 +1052,13 @@ void MainWindow::updateWindowState(bool force_visible)
// Update the display widget too if rendering separately.
if (m_display_surface && !isRenderingToMain())
{
#ifdef DISPLAY_SURFACE_WINDOW
QtUtils::SetWindowResizeable(m_display_surface, resizeable);
#else
QtUtils::SetWindowResizeable(m_display_container, resizeable);
#endif
}
}
void MainWindow::setProgressBar(int current, int total)
@@ -1074,12 +1093,12 @@ bool MainWindow::isRenderingFullscreen() const
if (!MTGS::IsOpen() || !m_display_surface)
return false;
return m_display_container->isFullScreen();
return m_display_surface->isFullScreen();
}
bool MainWindow::isRenderingToMain() const
{
return (m_display_surface && m_ui.mainContainer->indexOf(m_display_container) == 1);
return (m_display_container && m_ui.mainContainer->indexOf(m_display_container) == 1);
}
bool MainWindow::shouldHideMouseCursor() const
@@ -1107,16 +1126,25 @@ bool MainWindow::shouldMouseLock() const
if (m_display_created == false || m_display_surface == nullptr)
return false;
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
(!m_settings_window || m_settings_window->isHidden());
const bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
(!m_controller_settings_window || m_controller_settings_window->isHidden()) &&
(!m_settings_window || m_settings_window->isHidden());
auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window();
if (displayWindow == nullptr)
if (!windowsHidden)
return false;
return windowsHidden && (displayWindow->isActiveWindow() || displayWindow->isFullScreen());
#ifdef DISPLAY_SURFACE_WINDOW
if (isRenderingToMain())
{
const auto* displayWindow = window();
return displayWindow ? (displayWindow->isActiveWindow() || displayWindow->isFullScreen()) : false;
}
else
return m_display_surface->isActive() || m_display_surface->isFullScreen();
#else
const auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window();
return displayWindow ? (displayWindow->isActiveWindow() || displayWindow->isFullScreen()) : false;
#endif
}
bool MainWindow::shouldAbortForMemcardBusy(const VMLock& lock)
@@ -1179,7 +1207,7 @@ void MainWindow::switchToEmulationView()
g_emu_thread->setVMPaused(false);
if (m_display_surface)
m_display_container->setFocus();
m_display_surface->setFocus();
}
void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error)
@@ -1192,6 +1220,12 @@ void MainWindow::cancelGameListRefresh()
m_game_list_widget->cancelRefresh();
}
void MainWindow::updateGameListBackground()
{
if (m_game_list_widget)
m_game_list_widget->setCustomBackground();
}
void MainWindow::reportInfo(const QString& title, const QString& message)
{
QMessageBox::information(this, title, message);
@@ -1213,6 +1247,103 @@ void MainWindow::onStatusMessage(const QString& message)
m_ui.statusBar->showMessage(message);
}
void MainWindow::reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup)
{
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error)
{
SaveState_ReportLoadErrorOSD(message.toStdString(), slot, backup);
return;
}
QString title;
if (slot.has_value())
{
if (backup)
title = tr("Failed to Load State From Backup Slot %1").arg(*slot);
else
title = tr("Failed to Load State From Slot %1").arg(*slot);
}
else
{
title = tr("Failed to Load State");
}
VMLock lock(pauseAndLockVM());
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
QPointer<QMessageBox> message_box = new QMessageBox(this);
message_box->setWindowTitle(title);
message_box->setText(message);
message_box->setIcon(QMessageBox::Critical);
message_box->addButton(QMessageBox::Ok);
message_box->setDefaultButton(QMessageBox::Ok);
message_box->setCheckBox(do_not_show_again);
message_box->exec();
if (message_box.isNull())
return;
if (do_not_show_again->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
Host::CommitBaseSettingChanges();
if (m_settings_window)
{
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
}
}
delete message_box;
}
void MainWindow::reportStateSaveError(const QString& message, std::optional<s32> slot)
{
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error)
{
SaveState_ReportSaveErrorOSD(message.toStdString(), slot);
return;
}
QString title;
if (slot.has_value())
title = tr("Failed to Save State To Slot %1").arg(*slot);
else
title = tr("Failed to Save State");
VMLock lock(pauseAndLockVM());
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
QPointer<QMessageBox> message_box = new QMessageBox(this);
message_box->setWindowTitle(title);
message_box->setText(message);
message_box->setIcon(QMessageBox::Critical);
message_box->addButton(QMessageBox::Ok);
message_box->setDefaultButton(QMessageBox::Ok);
message_box->setCheckBox(do_not_show_again);
message_box->exec();
if (message_box.isNull())
return;
if (do_not_show_again->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
Host::CommitBaseSettingChanges();
if (m_settings_window)
{
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
}
}
delete message_box;
}
void MainWindow::runOnUIThread(const std::function<void()>& func)
{
func();
@@ -1734,8 +1865,8 @@ void MainWindow::onCreateGameShortcutTriggered()
const QString title = QString::fromStdString(entry->GetTitle());
const QString path = QString::fromStdString(entry->path);
VMLock lock(pauseAndLockVM());
ShortcutCreationDialog dlg(lock.getDialogParent(), title, path);
ShortcutCreationDialog dlg(this, title, path);
dlg.exec();
}
#endif
@@ -1876,7 +2007,7 @@ void MainWindow::onInputRecPlayActionTriggered()
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setWindowTitle("Select a File");
dialog.setWindowTitle(tr("Select a File"));
dialog.setNameFilter(tr("Input Recording Files (*.p2m2)"));
QStringList fileNames;
if (dialog.exec())
@@ -2025,7 +2156,7 @@ void MainWindow::onVMResumed()
if (m_display_surface)
{
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
}
}
@@ -2345,21 +2476,25 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
if (!is_fullscreen && !is_rendering_to_main)
saveDisplayWindowGeometryToConfig();
#ifdef DISPLAY_SURFACE_WINDOW
auto* displayWindow = m_display_surface;
#else
auto* displayWindow = m_display_container;
#endif
if (fullscreen)
{
m_display_container->showFullScreen();
}
displayWindow->showFullScreen();
else
{
// Needs to exit fullscreen before resizing
displayWindow->showNormal();
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_container->setGeometry(geometry());
displayWindow->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showNormal();
}
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
updateWindowState();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
@@ -2396,7 +2531,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
updateWindowState();
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
return wi;
}
@@ -2413,9 +2548,14 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
m_display_surface = new DisplaySurface();
if (fullscreen || !render_to_main)
{
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->setTitle(windowTitle());
m_display_surface->setIcon(windowIcon());
#else
m_display_container = m_display_surface->createWindowContainer();
m_display_container->setWindowTitle(windowTitle());
m_display_container->setWindowIcon(windowIcon());
#endif
}
else
{
@@ -2424,20 +2564,38 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
if (fullscreen)
{
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on.
// Other platforms can position windows fine, but the only thing that matters here is the screen.
#ifdef DISPLAY_SURFACE_WINDOW
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_surface->setFramePosition(pos());
else
restoreDisplayWindowGeometryFromConfig();
m_display_surface->showFullScreen();
#else
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_container->move(pos());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showFullScreen();
#endif
}
else if (!render_to_main)
{
#ifdef DISPLAY_SURFACE_WINDOW
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_surface->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_surface->showNormal();
#else
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_container->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showNormal();
#endif
}
else
{
@@ -2466,12 +2624,21 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
width = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(width) / dpr)), 1));
height = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(height) / dpr)), 1));
#ifdef DISPLAY_SURFACE_WINDOW
if (!m_display_container)
{
// no parent - rendering to separate window. easy.
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_surface, width, height);
return;
}
#else
if (!m_display_container->parent())
{
// no parent - rendering to separate window. easy.
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_container, width, height);
return;
}
#endif
// we are rendering to the main window. we have to add in the extra height from the toolbar/status bar.
const s32 extra_height = this->height() - m_display_container->height();
@@ -2504,7 +2671,7 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
if (!m_display_surface)
return;
if (!isRenderingFullscreen() && !isRenderingToMain())
if (!m_display_surface->isFullScreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
if (isRenderingToMain())
@@ -2518,12 +2685,18 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
}
}
// displau surface is always in a container
pxAssert(m_display_container != nullptr);
m_display_container->deleteLater();
m_display_container = nullptr;
// m_display_surface will be destroyed by the container's dtor
m_display_surface = nullptr;
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
// m_display_surface will be destroyed by the container's dtor
m_display_surface = nullptr;
}
else
{
m_display_surface->deleteLater();
m_display_surface = nullptr;
}
updateDisplayRelatedActions(false, false, false);
}
@@ -2535,14 +2708,6 @@ void MainWindow::updateDisplayWidgetCursor()
m_display_surface->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
}
void MainWindow::focusDisplayWidget()
{
if (!m_display_surface || centralWidget() != m_display_container)
return;
m_display_container->setFocus();
}
void MainWindow::setupMouseMoveHandler()
{
auto mouse_cb_fn = [](int x, int y) {
@@ -2573,7 +2738,11 @@ void MainWindow::checkMousePosition(int x, int y)
// logical (DIP) frame rect
const QSize logicalSize = displayWindow->size();
#ifdef DISPLAY_SURFACE_WINDOW
const QPoint logicalPosition = isRenderingToMain() ? (displayWindow->position() + displayWindow->parent()->position()) : displayWindow->position();
#else
const QPoint logicalPosition = displayWindow->position() + displayWindow->parent()->position();
#endif
// The offset to the origin of the current screen is in device-independent pixels while the origin itself is native!
// The logicalPosition is the sum of these two values, so we need to separate them and only scale the offset
@@ -2604,13 +2773,13 @@ void MainWindow::checkMousePosition(int x, int y)
void MainWindow::saveDisplayWindowGeometryToConfig()
{
if (m_display_container->windowState() & Qt::WindowFullScreen)
if (m_display_surface->isFullScreen())
{
// if we somehow ended up here, don't save the fullscreen state to the config
return;
}
const QByteArray geometry = m_display_container->saveGeometry();
const QByteArray geometry = m_display_surface->saveGeometry();
const QByteArray geometry_b64 = geometry.toBase64();
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
if (old_geometry_b64 != geometry_b64.constData())
@@ -2626,15 +2795,23 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
if (!geometry.isEmpty())
{
m_display_container->restoreGeometry(geometry);
m_display_surface->restoreGeometry(geometry);
// make sure we're not loading a dodgy config which had fullscreen set...
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->setWindowStates(m_display_surface->windowStates() & ~(Qt::WindowFullScreen | Qt::WindowActive));
#else
m_display_container->setWindowState(m_display_container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
#endif
}
else
{
// default size
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->resize(640, 480);
#else
m_display_container->resize(640, 480);
#endif
}
}
@@ -3058,7 +3235,7 @@ void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, con
}
const u32 deleted = VMManager::DeleteSaveStates(serial.toUtf8().constData(), crc, true);
QMessageBox::information(this, tr("Delete Save States"), tr("%1 save states deleted.").arg(deleted));
QMessageBox::information(this, tr("Delete Save States"), tr("%n save states deleted.", "", deleted));
});
}
}
@@ -3204,7 +3381,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
g_emu_thread->setFullscreen(false, false);
// Process events untill both EmuThread and Qt have finished exiting fullscreen
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_container->isFullScreen()))
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_surface->isFullScreen()))
{
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
@@ -3216,7 +3393,27 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
g_main_window->raise();
g_main_window->activateWindow();
return VMLock(m_display_container, was_paused, was_fullscreen);
#ifdef DISPLAY_SURFACE_WINDOW
if (!m_display_container)
{
// Create a temporary parent for the dialog.
QWidget* dialog_parent = new QWidget();
dialog_parent->setAttribute(Qt::WA_NativeWindow);
QWindow* window_handle = dialog_parent->windowHandle();
// Set the transient parent to the display surface.
// This will position the dialog_parent over the display surface (and thus so will any dialogs)
// and also enforces the focus lock of modal dialogs against the display surface.
// This works even without showing the dialog_parent window.
window_handle->setTransientParent(m_display_surface);
return VMLock(dialog_parent, was_paused, was_fullscreen, true);
}
else
return VMLock(m_display_container, was_paused, was_fullscreen, false);
#else
return VMLock(m_display_container, was_paused, was_fullscreen, false);
#endif
}
void MainWindow::rescanFile(const std::string& path)
@@ -3224,11 +3421,12 @@ void MainWindow::rescanFile(const std::string& path)
m_game_list_widget->rescanFile(path);
}
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen, bool owns_parent)
: m_dialog_parent(dialog_parent)
, m_has_lock(true)
, m_was_paused(was_paused)
, m_was_fullscreen(was_fullscreen)
, m_owns_dialog_parent(owns_parent)
{
QtHost::LockVMWithDialog();
}
@@ -3238,11 +3436,13 @@ MainWindow::VMLock::VMLock(VMLock&& lock)
, m_has_lock(lock.m_has_lock)
, m_was_paused(lock.m_was_paused)
, m_was_fullscreen(lock.m_was_fullscreen)
, m_owns_dialog_parent(lock.m_owns_dialog_parent)
{
lock.m_dialog_parent = nullptr;
lock.m_has_lock = false;
lock.m_was_paused = true;
lock.m_was_fullscreen = false;
lock.m_owns_dialog_parent = false;
}
MainWindow::VMLock::~VMLock()
@@ -3250,6 +3450,12 @@ MainWindow::VMLock::~VMLock()
if (m_has_lock)
QtHost::UnlockVMWithDialog();
if (m_owns_dialog_parent && m_dialog_parent)
{
m_dialog_parent->deleteLater();
m_dialog_parent = nullptr;
}
if (m_was_fullscreen)
{
g_main_window->m_is_temporarily_windowed = false;

View File

@@ -65,13 +65,14 @@ public:
void cancelResume();
private:
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen);
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen, bool owns_parent);
friend MainWindow;
QWidget* m_dialog_parent;
bool m_has_lock;
bool m_was_paused;
bool m_was_fullscreen;
bool m_owns_dialog_parent;
};
/// Default filter for opening a file.
@@ -116,10 +117,13 @@ public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache, bool popup_on_error);
void cancelGameListRefresh();
void updateGameListBackground();
void reportInfo(const QString& title, const QString& message);
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message);
void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup);
void reportStateSaveError(const QString& message, std::optional<s32> slot);
void runOnUIThread(const std::function<void()>& func);
void requestReset();
@@ -135,7 +139,6 @@ private Q_SLOTS:
void displayResizeRequested(qint32 width, qint32 height);
void mouseModeRequested(bool relative_mode, bool hide_cursor);
void releaseRenderWindow();
void focusDisplayWidget();
void setupMouseMoveHandler();
void onGameListRefreshComplete();
void onGameListRefreshProgress(const QString& status, int current, int total);

View File

@@ -1047,7 +1047,7 @@
<iconset theme="camera-video"/>
</property>
<property name="text">
<string>&amp;Video Capture</string>
<string comment="In Toolbar">&amp;Video Capture</string>
</property>
</action>
<action name="actionEditCheats">

View File

@@ -194,6 +194,9 @@ void EmuThread::stopFullscreenUI()
{
m_run_fullscreen_ui.store(false, std::memory_order_release);
emit onFullscreenUIStateChange(false);
// Resume and refresh background when FullscreenUI exits
QMetaObject::invokeMethod(g_main_window, "updateGameListBackground", Qt::QueuedConnection);
}
}
@@ -299,7 +302,11 @@ void EmuThread::loadState(const QString& filename)
Error error;
if (!VMManager::LoadState(filename.toUtf8().constData(), &error))
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
{
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription())]() {
g_main_window->reportStateLoadError(message, std::nullopt, false);
});
}
}
void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
@@ -315,7 +322,11 @@ void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
Error error;
if (!VMManager::LoadStateFromSlot(slot, load_backup, &error))
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
{
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription()), slot, load_backup]() {
g_main_window->reportStateLoadError(message, slot, load_backup);
});
}
}
void EmuThread::saveState(const QString& filename)
@@ -329,11 +340,11 @@ void EmuThread::saveState(const QString& filename)
if (!VMManager::HasValidVM())
return;
if (!VMManager::SaveState(filename.toUtf8().constData()))
{
// this one is usually the result of a user-chosen path, so we can display a message box safely here
Console.Error("Failed to save state");
}
VMManager::SaveState(filename.toUtf8().constData(), true, false, [](const std::string& error) {
QtHost::RunOnUIThread([message = QString::fromStdString(error)]() {
g_main_window->reportStateSaveError(message, std::nullopt);
});
});
}
void EmuThread::saveStateToSlot(qint32 slot)
@@ -347,7 +358,11 @@ void EmuThread::saveStateToSlot(qint32 slot)
if (!VMManager::HasValidVM())
return;
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
QtHost::RunOnUIThread([message = QString::fromStdString(error), slot]() {
g_main_window->reportStateSaveError(message, slot);
});
});
}
void EmuThread::run()
@@ -1586,7 +1601,7 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url
!FileSystem::WriteBinaryFile(path.c_str(), data.data(), data.size()))
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"),
qApp->translate("EmuThread", "Failed to write '%1'.").arg(QString::fromStdString(path)));
qApp->translate("EmuThread", "Failed to write downloaded data to file '%1'.").arg(QString::fromStdString(path)));
return false;
}

View File

@@ -141,9 +141,16 @@ namespace QtUtils
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity)
{
if (!pm || pm->isNull() || pm->width() <= 0 || pm->height() <= 0)
if (!pm || pm->width() <= 0 || pm->height() <= 0)
return;
if (dpr <= 0.0)
{
Console.ErrorFmt("resizeAndScalePixmap: Invalid device pixel ratio ({}) - pixmap will be null", dpr);
*pm = QPixmap();
return;
}
const int dpr_expected_width = qRound(expected_width * dpr);
const int dpr_expected_height = qRound(expected_height * dpr);
@@ -196,8 +203,7 @@ namespace QtUtils
qRound(scaledSize.width()),
qRound(scaledSize.height()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
);
Qt::SmoothTransformation);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
@@ -211,16 +217,7 @@ namespace QtUtils
}
case ScalingMode::Stretch:
{
*pm = pm->scaled(
dpr_expected_width,
dpr_expected_height,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
painter.drawPixmap(painterRect, *pm, scaledSrcRect);
painter.drawPixmap(painterRect, *pm, srcRect);
break;
}
case ScalingMode::Center:
@@ -229,7 +226,6 @@ namespace QtUtils
const qreal pmHeight = pm->height() / dpr;
QRectF destRect(0, 0, pmWidth, pmHeight);
destRect.moveCenter(painterRect.center());
painter.drawPixmap(destRect, *pm, srcRect);
@@ -243,13 +239,19 @@ namespace QtUtils
if (tileWidth <= 0 || tileHeight <= 0)
break;
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
tileSource.setDevicePixelRatio(dpr);
QBrush tileBrush(tileSource);
tileBrush.setTextureImage(tileSource.toImage());
painter.fillRect(painterRect, tileBrush);
if (pm->devicePixelRatio() == dpr)
{
QBrush tileBrush(*pm);
painter.fillRect(painterRect, tileBrush);
}
else
{
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
tileSource.setDevicePixelRatio(dpr);
QBrush tileBrush(tileSource);
tileBrush.setTextureImage(tileSource.toImage());
painter.fillRect(painterRect, tileBrush);
}
break;
}
default:
@@ -285,12 +287,12 @@ namespace QtUtils
{
#if defined(_WIN32)
//: Windows action to show a file in Windows Explorer
return QCoreApplication::translate("FileOperations", "Show in Folder");
return QCoreApplication::translate("FileOperations", "Show in Explorer");
#elif defined(__APPLE__)
//: macOS action to show a file in Finder
return QCoreApplication::translate("FileOperations", "Show in Finder");
#else
//: Opens the system file manager to the directory containing a selected file
//: Linux/*NIX: Opens the system file manager to the directory containing a selected file
return QCoreApplication::translate("FileOperations", "Open Containing Directory");
#endif
}
@@ -364,6 +366,25 @@ namespace QtUtils
}
}
void SetWindowResizeable(QWindow* window, bool resizeable)
{
if (resizeable)
{
// Min/max numbers come from uic.
window->setMinimumWidth(1);
window->setMinimumHeight(1);
window->setMaximumWidth(16777215);
window->setMaximumHeight(16777215);
}
else
{
window->setMinimumWidth(window->width());
window->setMinimumHeight(window->height());
window->setMaximumWidth(window->width());
window->setMaximumHeight(window->height());
}
}
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height)
{
width = std::max(width, 1);
@@ -374,6 +395,22 @@ namespace QtUtils
widget->resize(width, height);
}
void ResizePotentiallyFixedSizeWindow(QWindow* window, int width, int height)
{
width = std::max(width, 1);
height = std::max(height, 1);
if (window->minimumHeight() == window->maximumHeight())
{
window->setMinimumWidth(width);
window->setMinimumHeight(height);
window->setMaximumWidth(width);
window->setMaximumHeight(height);
}
window->resize(width, height);
}
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role, bool useQuotes)
{
QString csv;

View File

@@ -102,9 +102,11 @@ namespace QtUtils
/// Changes whether a window is resizable.
void SetWindowResizeable(QWidget* widget, bool resizeable);
void SetWindowResizeable(QWindow* window, bool resizeable);
/// Adjusts the fixed size for a window if it's not resizeable.
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
void ResizePotentiallyFixedSizeWindow(QWindow* window, int width, int height);
/// Returns the common window info structure for a Qt Window/Widget.
template <class T>

View File

@@ -1263,7 +1263,7 @@ namespace SettingWidgetBinder
static inline void BindWidgetToFileSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
const char* filter, bool allow_pergame = false, bool use_relative = true)
const QString& filter, bool allow_pergame = false, bool use_relative = true)
{
using Accessor = SettingAccessor<QLineEdit>;

View File

@@ -17,7 +17,8 @@ AchievementLoginDialog::AchievementLoginDialog(QWidget* parent, Achievements::Lo
, m_reason(reason)
{
m_ui.setupUi(this);
QtUtils::SetScalableIcon(m_ui.loginIcon, QIcon::fromTheme(QStringLiteral("login-box-line")), QSize(32, 32));
const QString base_path(QtHost::GetResourcesBasePath());
QtUtils::SetScalableIcon(m_ui.loginIcon, QIcon(QStringLiteral("%1/icons/ra-icon.svg").arg(base_path)), QSize(50, 50));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
// Adjust text if needed based on reason.

View File

@@ -17,7 +17,7 @@
#include <QtCore/QDateTime>
#include <QtWidgets/QMessageBox>
const char* AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "Audio Files (*.wav)");
const char* AchievementSettingsWidget::AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("AchievementSettingsWidget", "WAV Audio Files (*.wav)");
AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
: SettingsWidget(settings_dialog, parent)
@@ -44,9 +44,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_di
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.achievementNotificationsDuration, "Achievements", "NotificationsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.leaderboardNotificationsDuration, "Achievements", "LeaderboardsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
dialog()->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on startup."));
dialog()->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."));

View File

@@ -26,6 +26,7 @@ private Q_SLOTS:
private:
void updateLoginState();
static const char* AUDIO_FILE_FILTER;
Ui::AchievementSettingsWidget m_ui;
};

View File

@@ -115,7 +115,7 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* settings_dialog, QWidge
dialog()->registerWidgetHelp(m_ui.expansionSettings, tr("Expansion Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the FreeSurround-based channel expander."));
dialog()->registerWidgetHelp(m_ui.syncMode, tr("Synchronization"), tr("TimeStretch (Recommended)"),
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast-forward/slowdown audio."));
tr("When the emulation isn't running at 100% speed, adjusts the tempo of the audio which produces much nicer sound during fast-forward/slowdown."));
dialog()->registerWidgetHelp(m_ui.stretchSettings, tr("Stretch Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed."));
dialog()->registerWidgetHelp(m_ui.resetStandardVolume, tr("Reset Standard Volume"), tr("N/A"),
@@ -180,7 +180,7 @@ void AudioSettingsWidget::updateDriverNames()
const AudioBackend backend = getEffectiveBackend();
const std::vector<std::pair<std::string, std::string>> names = AudioStream::GetDriverNames(backend);
m_ui.driver->disconnect();
QObject::disconnect(m_ui.driver, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_ui.driver->clear();
if (names.empty())
{
@@ -208,7 +208,7 @@ void AudioSettingsWidget::updateDeviceNames()
const std::string current_device = dialog()->getEffectiveStringValue("SPU2/Output", "DeviceName", "");
const std::vector<AudioStream::DeviceInfo> devices = AudioStream::GetOutputDevices(backend, driver_name.c_str());
m_ui.outputDevice->disconnect();
QObject::disconnect(m_ui.outputDevice, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_ui.outputDevice->clear();
m_output_device_latency = 0;

View File

@@ -39,6 +39,8 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* settings_dialog, QWidget*
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
connect(m_ui.fileList, &QTreeWidget::currentItemChanged, this, &BIOSSettingsWidget::listItemChanged);
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::fastBootChanged);
fastBootChanged();
}
BIOSSettingsWidget::~BIOSSettingsWidget() = default;

View File

@@ -30,7 +30,7 @@ DEV9DnsHostDialog::DEV9DnsHostDialog(std::vector<HostEntryUi> hosts, QWidget* pa
QStringList headers;
headers.push_back(tr("Selected"));
headers.push_back(tr("Name"));
headers.push_back(tr("Url"));
headers.push_back(tr("Hostname"));
headers.push_back(tr("Address"));
headers.push_back(tr("Enabled"));
m_ethHost_model->setHorizontalHeaderLabels(headers);

View File

@@ -139,7 +139,7 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget*
QStringList headers;
headers.push_back(tr("Name"));
headers.push_back(tr("Url"));
headers.push_back(tr("Hostname"));
headers.push_back(tr("Address"));
headers.push_back(tr("Enabled"));
m_ethHost_model->setHorizontalHeaderLabels(headers);
@@ -792,7 +792,7 @@ void DEV9SettingsWidget::showEvent(QShowEvent* event)
* hidden, maybe because our table is nested within group & tab widgets
* We could also listern to out show event, but we also need to listern to the tab
* changed signal, in the event that another tab is selected when our ui is shown
*
*
* Instead, lets use an eventFilter to determine exactly when the host table is shown
* However, the eventFilter is ran before the widgets event handler, meaning the table
* is still the wrong size, so we also need to check the show event

View File

@@ -83,7 +83,7 @@
<item>
<widget class="QLabel" name="symbolSourceErrorMessage">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>

View File

@@ -14,6 +14,7 @@
#include "common/HeterogeneousContainers.h"
#include <QtCore/QSortFilterProxyModel>
#include <QtGui/QMouseEvent>
#include <QtGui/QStandardItemModel>
GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
@@ -40,6 +41,9 @@ GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* settings_dialog
m_ui.cheatList->expandAll();
m_ui.cheatList->viewport()->installEventFilter(this);
m_ui.cheatList->viewport()->setMouseTracking(true);
SettingsInterface* sif = dialog()->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowCheatsForAllCRCs", false);
@@ -83,7 +87,7 @@ void GameCheatSettingsWidget::onCheatListItemDoubleClicked(const QModelIndex& in
return;
}
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (!data.isValid())
return;
@@ -95,7 +99,7 @@ void GameCheatSettingsWidget::onCheatListItemDoubleClicked(const QModelIndex& in
void GameCheatSettingsWidget::onCheatListItemChanged(QStandardItem* item)
{
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (!data.isValid())
return;
@@ -109,6 +113,31 @@ void GameCheatSettingsWidget::onCheatListItemChanged(QStandardItem* item)
setCheatEnabled(std::move(cheat_name), current_checked, true);
}
void GameCheatSettingsWidget::onCheatListItemHovered(const QModelIndex& index)
{
const QModelIndex source_index = m_model_proxy->mapToSource(index);
const QModelIndex sibling_index = source_index.siblingAtColumn(0);
QStandardItem* item = m_model->itemFromIndex(sibling_index);
if (!item)
{
// No item is selected.
m_ui.appliedLabel->clear();
return;
}
std::optional<Patch::patch_place_type> place;
bool ok;
int place_value = item->data(PLACE_ROLE).toInt(&ok);
if (ok)
{
// The patch commands in the group are all applied at the same time.
place = static_cast<Patch::patch_place_type>(place_value);
}
m_ui.appliedLabel->setText(tr("<strong>Applied:</strong> %1").arg(Patch::PlaceToString(place)));
}
void GameCheatSettingsWidget::onReloadClicked()
{
reloadList();
@@ -136,6 +165,32 @@ void GameCheatSettingsWidget::disableAllCheats()
si->Save();
}
bool GameCheatSettingsWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_ui.cheatList->viewport())
{
switch (event->type())
{
case QEvent::MouseMove:
{
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
onCheatListItemHovered(m_ui.cheatList->indexAt(mouse_event->position().toPoint()));
return true;
}
case QEvent::Leave:
{
onCheatListItemHovered(QModelIndex());
return true;
}
default:
{
}
}
}
return SettingsWidget::eventFilter(watched, event);
}
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
@@ -185,7 +240,7 @@ void GameCheatSettingsWidget::setStateRecursively(QStandardItem* parent, bool en
for (int i = 0; i < count; i++)
{
QStandardItem* item = parent ? parent->child(i, 0) : m_model->item(i, 0);
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (data.isValid())
{
if ((item->checkState() == Qt::Checked) != enabled)
@@ -277,7 +332,9 @@ QList<QStandardItem*> GameCheatSettingsWidget::populateTreeViewRow(const Patch::
const std::string_view name_part = pi.GetNamePart();
nameItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren | Qt::ItemIsEnabled);
nameItem->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
nameItem->setData(QString::fromStdString(pi.name), Qt::UserRole);
nameItem->setData(QString::fromStdString(pi.name), NAME_ROLE);
if (pi.place.has_value())
nameItem->setData(static_cast<int>(*pi.place), PLACE_ROLE);
if (!name_part.empty())
nameItem->setText(QString::fromUtf8(name_part.data(), name_part.length()));

View File

@@ -32,6 +32,7 @@ public:
~GameCheatSettingsWidget();
void disableAllCheats();
bool eventFilter(QObject* watched, QEvent* event) override;
protected:
void resizeEvent(QResizeEvent* event) override;
@@ -39,6 +40,7 @@ protected:
private Q_SLOTS:
void onCheatListItemDoubleClicked(const QModelIndex& index);
void onCheatListItemChanged(QStandardItem* item);
void onCheatListItemHovered(const QModelIndex& index);
void onReloadClicked();
void updateListEnabled();
void reloadList();
@@ -50,6 +52,12 @@ private:
void setStateForAll(bool enabled);
void setStateRecursively(QStandardItem* parent, bool enabled);
enum Roles
{
NAME_ROLE = Qt::UserRole,
PLACE_ROLE = Qt::UserRole + 1
};
Ui::GameCheatSettingsWidget m_ui;
QStandardItemModel* m_model = nullptr;
QSortFilterProxyModel* m_model_proxy = nullptr;

View File

@@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="appliedLabel">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">

View File

@@ -44,7 +44,7 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* settings_dialog, QW
dialog()->registerWidgetHelp(m_ui.EETimingHack, tr("EE Timing Hack"), tr("Unchecked"), tr("General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX."));
dialog()->registerWidgetHelp(m_ui.InstantDMAHack, tr("Instant DMA Hack"), tr("Unchecked"), tr("Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z."));
dialog()->registerWidgetHelp(m_ui.DMABusyHack, tr("DMA Busy Hack"), tr("Unchecked"), tr("Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines."));
dialog()->registerWidgetHelp(m_ui.GIFFIFOHack, tr("Emulate GIF FIFO"), tr("Unchecked"), tr("Correct but slower. Known to affect the following games: Fifa Street 2."));
dialog()->registerWidgetHelp(m_ui.GIFFIFOHack, tr("Emulate GIF FIFO"), tr("Unchecked"), tr("Correct but slower. Known to affect the following games: FIFA Street 2."));
dialog()->registerWidgetHelp(m_ui.VIFFIFOHack, tr("Emulate VIF FIFO"), tr("Unchecked"), tr("Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers."));
dialog()->registerWidgetHelp(m_ui.VIF1StallHack, tr("Delay VIF1 Stalls"), tr("Unchecked"), tr("For SOCOM 2 HUD and Spy Hunter loading hang."));
dialog()->registerWidgetHelp(m_ui.VuAddSubHack, tr("VU Add Hack"), tr("Unchecked"), tr("For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2."));

View File

@@ -15,19 +15,23 @@
#include <algorithm>
GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::string& author,
const std::string& description, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent)
GamePatchDetailsWidget::GamePatchDetailsWidget(const Patch::PatchInfo& info, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent)
: QWidget(parent)
, m_dialog(dialog)
, m_name(name)
, m_name(info.name)
{
m_ui.setupUi(this);
m_ui.name->setText(QString::fromStdString(name));
const QString name = QString::fromStdString(info.name);
const QString author = !info.author.empty() ? QString::fromStdString(info.author) : tr("Unknown");
const QString place = QString::fromUtf8(PlaceToString(info.place));
const QString description = !info.description.empty() ? QString::fromStdString(info.description) : tr("No description provided.");
m_ui.name->setText(name);
m_ui.description->setText(
tr("<strong>Author: </strong>%1<br>%2")
.arg(author.empty() ? tr("Unknown") : QString::fromStdString(author))
.arg(description.empty() ? tr("No description provided.") : QString::fromStdString(description)));
tr("<strong>Author:</strong> %1<br><strong>Applied:</strong> %2<br>%3")
.arg(author)
.arg(place)
.arg(description));
pxAssert(dialog->getSettingsInterface());
m_ui.enabled->setTristate(tristate);
@@ -178,7 +182,7 @@ void GamePatchSettingsWidget::reloadList()
}
GamePatchDetailsWidget* it =
new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, globally_toggleable_option, check_state, dialog(), container);
new GamePatchDetailsWidget(pi, globally_toggleable_option, check_state, dialog(), container);
layout->addWidget(it);
}
}

View File

@@ -20,7 +20,7 @@ class GamePatchDetailsWidget : public QWidget
Q_OBJECT
public:
GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool tristate, Qt::CheckState checkState,
GamePatchDetailsWidget(const Patch::PatchInfo& info, bool tristate, Qt::CheckState checkState,
SettingsWindow* dialog, QWidget* parent);
~GamePatchDetailsWidget();

View File

@@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="screenshotGroupBox">
<property name="title">
<string>Screenshot Capture Setup</string>
</property>
@@ -42,7 +42,7 @@
</property>
<item>
<property name="text">
<string>Window Resolution (Aspect Corrected)</string>
<string>Display Resolution (Aspect Corrected)</string>
</property>
</item>
<item>
@@ -186,6 +186,9 @@
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>67</number>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
@@ -273,6 +276,9 @@
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>420</number>
</property>
</widget>
</item>
<item row="3" column="0">
@@ -325,7 +331,7 @@
<number>16</number>
</property>
<property name="value">
<number>240</number>
<number>480</number>
</property>
</widget>
</item>

View File

@@ -472,7 +472,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_display.interlacing, tr("Deinterlacing"), tr("Automatic (Default)"), tr("Determines the deinterlacing method to be used on the interlaced screen of the emulated console. Automatic should be able to correctly deinterlace most games, but if you see visibly shaky graphics, try one of the other options."));
dialog()->registerWidgetHelp(m_capture.screenshotSize, tr("Screenshot Resolution"), tr("Screen Resolution"),
dialog()->registerWidgetHelp(m_capture.screenshotSize, tr("Screenshot Resolution"), tr("Display Resolution"),
tr("Determines the resolution at which screenshots will be saved. Internal resolutions preserve more detail at the cost of "
"file size."));
@@ -587,10 +587,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_fixes.gpuTargetCLUTMode, tr("GPU Target CLUT"), tr("Disabled"),
tr("Tries to detect when a game is drawing its own color palette and then renders it on the GPU with special handling."));
dialog()->registerWidgetHelp(m_fixes.skipDrawStart, tr("Skipdraw Range Start"), tr("0"),
dialog()->registerWidgetHelp(m_fixes.skipDrawStart, tr("Skip Draw Range Start"), tr("0"),
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
dialog()->registerWidgetHelp(m_fixes.skipDrawEnd, tr("Skipdraw Range End"), tr("0"),
dialog()->registerWidgetHelp(m_fixes.skipDrawEnd, tr("Skip Draw Range End"), tr("0"),
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
dialog()->registerWidgetHelp(m_fixes.hwAutoFlush, tr("Auto Flush"), tr("Unchecked"),
@@ -660,7 +660,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
//: Wild Arms: name of a game series. Leave as-is or use an official translation.
tr("Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games."));
dialog()->registerWidgetHelp(m_upscaling.bilinearHack, tr("Bilinear Upscale"), tr("Unchecked"),
dialog()->registerWidgetHelp(m_upscaling.bilinearHack, tr("Bilinear Dirty Upscale"), tr("Unchecked"),
tr("Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare."));
dialog()->registerWidgetHelp(m_upscaling.mergeSprite, tr("Merge Sprite"), tr("Unchecked"),
@@ -729,7 +729,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
tr("Shows the number of internal video frames displayed per second by the system."));
dialog()->registerWidgetHelp(m_osd.showVPS, tr("Show VPS"), tr("Unchecked"),
tr("Shows the number of V-syncs performed per second by the system."));
tr("Shows the number of Vsyncs performed per second by the system."));
dialog()->registerWidgetHelp(m_osd.showResolution, tr("Show Resolution"), tr("Unchecked"),
tr("Shows the internal resolution of the game."));
@@ -765,10 +765,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
tr("Shows the current controller state of the system in the bottom-left corner of the display."));
dialog()->registerWidgetHelp(m_osd.showVideoCapture, tr("Show Video Capture Status"), tr("Checked"),
tr("Shows the status of the currently active video capture in the top-right corner of the display.."));
tr("Shows the status of the currently active video capture in the top-right corner of the display."));
dialog()->registerWidgetHelp(m_osd.showInputRec, tr("Show Input Recording Status"), tr("Checked"),
tr("Shows the status of the currently active input recording in the top-right corner of the display.."));
tr("Shows the status of the currently active input recording in the top-right corner of the display."));
dialog()->registerWidgetHelp(m_osd.showTextureReplacements, tr("Show Texture Replacement Status"), tr("Unchecked"),
tr("Shows the status of the number of dumped and loaded texture replacements in the top-right corner of the display."));
@@ -981,7 +981,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
const std::string container(
dialog()->getEffectiveStringValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER));
m_capture.videoCaptureCodec->disconnect();
QObject::disconnect(m_capture.videoCaptureCodec, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.videoCaptureCodec->clear();
//: This string refers to a default codec, whether it's an audio codec or a video codec.
m_capture.videoCaptureCodec->addItem(tr("Default"), QString());
@@ -996,7 +996,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
dialog()->getSettingsInterface(), m_capture.videoCaptureCodec, "EmuCore/GS", "VideoCaptureCodec");
connect(m_capture.videoCaptureCodec, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onCaptureCodecChanged);
m_capture.audioCaptureCodec->disconnect();
QObject::disconnect(m_capture.audioCaptureCodec, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.audioCaptureCodec->clear();
m_capture.audioCaptureCodec->addItem(tr("Default"), QString());
for (const auto& [format, name] : GSCapture::GetAudioCodecList(container.c_str()))
@@ -1012,7 +1012,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
void GraphicsSettingsWidget::GraphicsSettingsWidget::onCaptureCodecChanged()
{
m_capture.videoCaptureFormat->disconnect();
QObject::disconnect(m_capture.videoCaptureFormat, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.videoCaptureFormat->clear();
//: This string refers to a default pixel format
m_capture.videoCaptureFormat->addItem(tr("Default"), "");

View File

@@ -93,7 +93,7 @@
<item row="1" column="0">
<widget class="QLabel" name="nativeScalingLabel">
<property name="text">
<string>Native Scaling</string>
<string>Native Scaling:</string>
</property>
<property name="buddy">
<cstring>nativeScaling</cstring>

View File

@@ -22,9 +22,6 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="title">
<property name="text">
<string>Bindings for Controller0/ButtonCircle</string>
</property>
<property name="buddy">
<cstring>bindingList</cstring>
</property>

View File

@@ -97,15 +97,16 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.promptOnStateLoadSaveFailure, "UI", "PromptOnStateLoadSaveFailure", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
#ifdef __linux__ // Mouse locking is only supported on X11
#ifdef __linux__ // Mouse locking is only supported on X11
const bool mouse_lock_supported = QGuiApplication::platformName().toLower() == "xcb";
#else
const bool mouse_lock_supported = true;
#endif
if(mouse_lock_supported)
if (mouse_lock_supported)
{
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false);
connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) {
@@ -196,6 +197,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
"and unpauses when you switch back."));
dialog()->registerWidgetHelp(m_ui.pauseOnControllerDisconnection, tr("Pause On Controller Disconnection"),
tr("Unchecked"), tr("Pauses the emulator when a controller with bindings is disconnected."));
dialog()->registerWidgetHelp(m_ui.promptOnStateLoadSaveFailure, tr("Pause On State Load/Save Failure"),
tr("Checked"), tr("Display a modal dialog when a save state load/save operation fails."));
dialog()->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"),
tr("Automatically switches to fullscreen mode when a game is started."));
dialog()->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Unchecked"),
@@ -224,7 +227,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
dialog()->registerWidgetHelp(
m_ui.backgroundBrowse, tr("Game List Background"), tr("None"),
tr("Enable an animated/static background on the game list (where you launch your games).<br>"
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
dialog()->registerWidgetHelp(
m_ui.backgroundReset, tr("Disable/Reset Game List Background"), tr("None"),
tr("Disable and reset the currently applied game list background."));
@@ -241,6 +244,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
InterfaceSettingsWidget::~InterfaceSettingsWidget() = default;
void InterfaceSettingsWidget::updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state)
{
QSignalBlocker blocker(m_ui.promptOnStateLoadSaveFailure);
m_ui.promptOnStateLoadSaveFailure->setCheckState(state);
}
void InterfaceSettingsWidget::onRenderToSeparateWindowChanged()
{
m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
@@ -261,12 +270,12 @@ void InterfaceSettingsWidget::populateLanguages()
void InterfaceSettingsWidget::onSetGameListBackgroundTriggered()
{
const QString path = QDir::toNativeSeparators(
QFileDialog::getOpenFileName(this, tr("Select Background Image"), QString(), IMAGE_FILE_FILTER));
QFileDialog::getOpenFileName(this, tr("Select Background Image"), QString(), qApp->translate("InterfaceSettingsWidget", IMAGE_FILE_FILTER)));
if (path.isEmpty())
return;
std::string relative_path = Path::MakeRelative(QDir::toNativeSeparators(path).toStdString(), EmuFolders::DataRoot);
std::string relative_path = Path::MakeRelative(path.toStdString(), EmuFolders::DataRoot);
Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str());
Host::CommitBaseSettingChanges();

View File

@@ -15,6 +15,8 @@ public:
InterfaceSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent);
~InterfaceSettingsWidget();
void updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state);
Q_SIGNALS:
void themeChanged();
void languageChanged();

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>725</width>
<height>625</height>
<height>637</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -78,6 +78,13 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="promptOnStateLoadSaveFailure">
<property name="text">
<string>Prompt On State Load/Save Failure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -380,6 +387,7 @@
<tabstop>discordPresence</tabstop>
<tabstop>pauseOnControllerDisconnection</tabstop>
<tabstop>mouseLock</tabstop>
<tabstop>promptOnStateLoadSaveFailure</tabstop>
<tabstop>startFullscreen</tabstop>
<tabstop>doubleClickTogglesFullscreen</tabstop>
<tabstop>renderToSeparateWindow</tabstop>

View File

@@ -261,8 +261,10 @@ void SetupWizardDialog::onDirectoryListContextMenuRequested(const QPoint& point)
const int row = selection[0].row();
QMenu menu;
//: Part of the right-click menu for game directory entries
menu.addAction(tr("Remove"), [this]() { onRemoveSearchDirectoryButtonClicked(); });
menu.addSeparator();
//: Part of the right-click menu for game directory entries
menu.addAction(tr("Open Directory..."),
[this, row]() { QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectoryList->item(row, 0)->text())); });
menu.exec(m_ui.searchDirectoryList->mapToGlobal(point));

View File

@@ -13,7 +13,7 @@
#include "VMManager.h"
#if defined(_WIN32)
#include <Windows.h>
#include "common/RedtapeWindows.h"
#include <shlobj.h>
#include <winnls.h>
#include <shobjidl.h>
@@ -61,16 +61,6 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
connect(m_ui.bootOptionToggle, &QCheckBox::toggled, m_ui.bootOptionDropdown, &QPushButton::setEnabled);
connect(m_ui.fullscreenMode, &QCheckBox::toggled, m_ui.fullscreenModeDropdown, &QPushButton::setEnabled);
m_ui.shortcutDesktop->setChecked(true);
m_ui.overrideBootELFPath->setEnabled(false);
m_ui.overrideBootELFButton->setEnabled(false);
m_ui.gameArgs->setEnabled(false);
m_ui.bootOptionDropdown->setEnabled(false);
m_ui.fullscreenModeDropdown->setEnabled(false);
m_ui.loadStateIndex->setEnabled(false);
m_ui.loadStateFileBrowse->setEnabled(false);
m_ui.loadStateFilePath->setEnabled(false);
m_ui.loadStateIndex->setMaximum(VMManager::NUM_SAVE_STATE_SLOTS);
if (std::getenv("container"))
@@ -80,6 +70,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
m_ui.shortcutStartMenu->setEnabled(false);
}
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() {
std::vector<std::string> args;
@@ -111,21 +102,24 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
}
}
if (m_ui.loadStateFileToggle->isChecked() && !m_ui.loadStateFilePath->text().isEmpty())
{
args.push_back("-statefile");
args.push_back(m_ui.loadStateFilePath->text().toStdString());
}
if (m_ui.fullscreenMode->isChecked())
args.push_back(m_ui.fullscreenModeDropdown->currentIndex() ? "-nofullscreen" : "-fullscreen");
if (m_ui.bigPictureModeToggle->isChecked())
args.push_back("-bigpicture");
m_desktop = m_ui.shortcutDesktop->isChecked();
std::string custom_args = m_ui.customArgsInput->text().toStdString();
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_desktop);
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_ui.shortcutDesktop->isChecked());
accept();
});
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, bool is_desktop)
@@ -183,7 +177,10 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
{
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
@@ -203,7 +200,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1)").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
@@ -313,9 +310,6 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
return;
}
if (is_flatpak) // Flatpak
executable_path = "flatpak run net.pcsx2.PCSX2";
// Find home directory
std::string link_path;
const char* home = std::getenv("HOME");
@@ -340,15 +334,30 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Path to the Home directory is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Checks if a shortcut already exist
if (FileSystem::FileExists(link_path.c_str()))
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
std::string icon_name;
if (is_flatpak) // Flatpak
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
icon_name = "PCSX2";
std::string icon_path = fmt::format("{}/{}.png", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
}
// Shortcut CmdLine Args
@@ -357,25 +366,13 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
{
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
if (!is_flatpak)
{
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
std::string icon_name = "PCSX2.png";
std::string icon_path = fmt::format("{}/{}", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
}
// Further string sanitization
if (!is_flatpak)
ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path);
@@ -393,7 +390,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
"StartupWMClass=PCSX2\n"
"Exec=" + final_args + "\n"
"Name=" + clean_name + "\n"
"Icon=net.pcsx2.PCSX2\n"
"Icon=" + icon_name + "\n"
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);

View File

@@ -22,8 +22,9 @@ public:
bool EscapeShortcutCommandLine(std::string* cmdline);
protected:
QString m_title;
QString m_path;
bool m_desktop;
const QString m_title;
const QString m_path;
private:
Ui::ShortcutCreationDialog m_ui;
};

View File

@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>500</width>
<height>685</height>
<height>700</height>
</rect>
</property>
<layout class="QGridLayout" name="shortcutLayout">
@@ -59,6 +59,9 @@
</item>
<item row="0" column="1">
<widget class="QComboBox" name="fullscreenModeDropdown">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Force Enable</string>
@@ -82,10 +85,17 @@
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="QLineEdit" name="gameArgs"/>
<widget class="QLineEdit" name="gameArgs">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="2" colspan="2">
<widget class="QComboBox" name="bootOptionDropdown">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Fast Boot</string>
@@ -107,13 +117,20 @@
</item>
<item row="2" column="3">
<widget class="QPushButton" name="overrideBootELFButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="overrideBootELFPath"/>
<widget class="QLineEdit" name="overrideBootELFPath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="10" column="0" colspan="4">
<widget class="QGroupBox" name="saveStateGroup">
@@ -123,6 +140,9 @@
<layout class="QGridLayout" name="savestateGridLayout">
<item row="1" column="1">
<widget class="QSpinBox" name="loadStateIndex">
<property name="enabled">
<bool>false</bool>
</property>
<property name="showGroupSeparator" stdset="0">
<bool>false</bool>
</property>
@@ -132,7 +152,11 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="loadStateFilePath"/>
<widget class="QLineEdit" name="loadStateFilePath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="loadStateNone">
@@ -160,6 +184,9 @@
</item>
<item row="2" column="2">
<widget class="QPushButton" name="loadStateFileBrowse">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse...</string>
</property>
@@ -194,7 +221,7 @@
<item row="0" column="0">
<widget class="QLabel" name="customArgsInstruction">
<property name="text">
<string>You may add additional (space-separated) &lt;a href=&quot;https://pcsx2.net/docs/post/cli/&quot;&gt;custom arguments&lt;/a&gt; that are not listed above here:</string>
<string>You may add additional (space-separated) &lt;a href=&quot;https://pcsx2.net/docs/advanced/cli/&quot;&gt;custom arguments&lt;/a&gt; that are not listed above here:</string>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::RichText</enum>

View File

@@ -82,7 +82,7 @@ void InputRecordingViewer::openFile()
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setWindowTitle("Select a File");
dialog.setWindowTitle(tr("Select a File"));
dialog.setNameFilter(tr("Input Recording Files (*.p2m2)"));
QStringList fileNames;
if (dialog.exec())

View File

@@ -4,8 +4,8 @@
<context>
<name>AchievementSettingsWidget</name>
<message numerus="yes">
<location filename="../Settings/AchievementSettingsWidget.cpp" line="134"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="141"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="196"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="203"/>
<source>%n seconds</source>
<translation>
<numerusform>%n second</numerusform>
@@ -16,7 +16,7 @@
<context>
<name>Achievements</name>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1023"/>
<location filename="../../pcsx2/Achievements.cpp" line="1104"/>
<source>You have unlocked {} of %n achievements</source>
<comment>Achievement popup</comment>
<translation>
@@ -25,7 +25,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1026"/>
<location filename="../../pcsx2/Achievements.cpp" line="1107"/>
<source>and earned {} of %n points</source>
<comment>Achievement popup</comment>
<translation>
@@ -34,7 +34,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1111"/>
<location filename="../../pcsx2/Achievements.cpp" line="1191"/>
<source>%n achievements</source>
<comment>Mastery popup</comment>
<translation>
@@ -43,7 +43,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1113"/>
<location filename="../../pcsx2/Achievements.cpp" line="1193"/>
<source>%n points</source>
<comment>Mastery popup</comment>
<translation>
@@ -55,8 +55,8 @@
<context>
<name>GameList</name>
<message numerus="yes">
<location filename="../GameList/GameListModel.cpp" line="268"/>
<location filename="../../pcsx2/GameList.cpp" line="1142"/>
<location filename="../GameList/GameListModel.cpp" line="223"/>
<location filename="../../pcsx2/GameList.cpp" line="1248"/>
<source>%n hours</source>
<translation>
<numerusform>%n hour</numerusform>
@@ -64,19 +64,27 @@
</translation>
</message>
<message numerus="yes">
<location filename="../GameList/GameListModel.cpp" line="270"/>
<location filename="../../pcsx2/GameList.cpp" line="1144"/>
<location filename="../GameList/GameListModel.cpp" line="227"/>
<location filename="../../pcsx2/GameList.cpp" line="1250"/>
<source>%n minutes</source>
<translation>
<numerusform>%n minute</numerusform>
<numerusform>%n minutes</numerusform>
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/GameList.cpp" line="1252"/>
<source>%n seconds</source>
<translation>
<numerusform>%n second</numerusform>
<numerusform>%n seconds</numerusform>
</translation>
</message>
</context>
<context>
<name>InputBindingWidget</name>
<message numerus="yes">
<location filename="../Settings/InputBindingWidget.cpp" line="73"/>
<location filename="../Settings/InputBindingWidget.cpp" line="77"/>
<source>%n bindings</source>
<translation>
<numerusform>%n binding</numerusform>
@@ -84,10 +92,21 @@
</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message numerus="yes">
<location filename="../MainWindow.cpp" line="3061"/>
<source>%n save states deleted.</source>
<translation>
<numerusform>%n save state deleted.</numerusform>
<numerusform>%n save states deleted.</numerusform>
</translation>
</message>
</context>
<context>
<name>Patch</name>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="698"/>
<location filename="../../pcsx2/Patch.cpp" line="768"/>
<source>%n GameDB patches are active.</source>
<comment>OSD Message</comment>
<translation>
@@ -96,7 +115,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="705"/>
<location filename="../../pcsx2/Patch.cpp" line="775"/>
<source>%n game patches are active.</source>
<comment>OSD Message</comment>
<translation>
@@ -105,7 +124,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="712"/>
<location filename="../../pcsx2/Patch.cpp" line="781"/>
<source>%n cheat patches are active.</source>
<comment>OSD Message</comment>
<translation>

File diff suppressed because it is too large Load Diff

View File

@@ -2942,8 +2942,19 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[bit_ofs - 0x20], 0x10);
memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[bit_ofs - 0x10], 0x10);
const size_t buf_size = sizeof(cdvd.mg_buffer);
if (bit_ofs < 0x20 || (size_t)bit_ofs > buf_size)
{
fail_pol_cal();
break;
}
const size_t kbit_ofs = bit_ofs - 0x20;
const size_t kcon_ofs = bit_ofs - 0x10;
std::memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[kbit_ofs], 0x10);
std::memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[kcon_ofs], 0x10);
if ((cdvd.mg_buffer[bit_ofs + 5] || cdvd.mg_buffer[bit_ofs + 6] || cdvd.mg_buffer[bit_ofs + 7]) ||
(GetBufferU16(&cdvd.mg_buffer[0],bit_ofs + 4) * 16 + bit_ofs + 8 + 16 != GetBufferU16(&cdvd.mg_buffer[0], 0x14)))
@@ -2969,7 +2980,31 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
{
SetSCMDResultSize(3); //in:0
const int bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
memcpy(&cdvd.mg_buffer[0], &cdvd.mg_buffer[bit_ofs], static_cast<size_t>(8 + 16 * static_cast<int>(cdvd.mg_buffer[bit_ofs + 4])));
if (bit_ofs < 0)
{
fail_pol_cal();
break;
}
const size_t bufsize = sizeof(cdvd.mg_buffer);
const size_t ofs = static_cast<size_t>(bit_ofs);
if (ofs > bufsize - 5) // Make sure we can read the block count
{
fail_pol_cal();
break;
}
const unsigned int blocks = static_cast<unsigned int>(cdvd.mg_buffer[ofs + 4]);
const size_t copy_len = 8 + 16 * static_cast<size_t>(blocks);
if (copy_len > bufsize - ofs) // Make sure we can read the blocks
{
fail_pol_cal();
break;
}
std::memmove(&cdvd.mg_buffer[0], &cdvd.mg_buffer[ofs], copy_len);
cdvd.mg_maxsize = 0; // don't allow any write
cdvd.mg_size = 8 + 16 * cdvd.mg_buffer[4]; //new offset, i just moved the data

View File

@@ -513,7 +513,7 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
if (m_feature_level < D3D_FEATURE_LEVEL_11_0)
{
Host::AddIconOSDMessage("d3d11_feature_level_warning", ICON_FA_TRIANGLE_EXCLAMATION,
TRANSLATE_SV("GS", "The Direct3D11 renderer is running at feature level 10.0. This is an UNSUPPORTED configuration.\n"
TRANSLATE_SV("GS", "The Direct3D 11 renderer is running at feature level 10.0. This is an UNSUPPORTED configuration.\n"
"Do not request support, please upgrade your hardware/drivers first."),
Host::OSD_WARNING_DURATION);
}

View File

@@ -165,11 +165,12 @@ bool GSDevice12::CreateDevice(u32& vendor_id)
// Enabling the debug layer will fail if the Graphics Tools feature is not installed.
if (enable_debug_layer)
{
ComPtr<ID3D12Debug> debug12;
ComPtr<ID3D12Debug1> debug12;
hr = D3D12GetDebugInterface(IID_PPV_ARGS(debug12.put()));
if (SUCCEEDED(hr))
{
debug12->EnableDebugLayer();
debug12->SetEnableGPUBasedValidation(true);
}
else
{
@@ -342,12 +343,14 @@ bool GSDevice12::CreateCommandLists()
void GSDevice12::MoveToNextCommandList()
{
m_current_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
m_current_fence_value++;
const int next_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
// We may have to wait if this command list hasn't finished on the GPU.
CommandListResources& res = m_command_lists[m_current_command_list];
CommandListResources& res = m_command_lists[next_command_list];
WaitForFence(res.ready_fence_value, false);
m_current_command_list = next_command_list;
m_current_fence_value++;
res.ready_fence_value = m_current_fence_value;
res.init_command_list_used = false;
@@ -1224,8 +1227,8 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id)
{
//const bool isAMD = (vendor_id == 0x1002 || vendor_id == 0x1022);
m_features.texture_barrier = false;
m_features.multidraw_fb_copy = GSConfig.OverrideTextureBarriers != 0;
m_features.texture_barrier = GSConfig.OverrideTextureBarriers != 0;
m_features.multidraw_fb_copy = false;
m_features.broken_point_sampler = false;
m_features.primitive_id = true;
m_features.prefer_new_textures = true;
@@ -2178,13 +2181,13 @@ void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
m_index_stream_buffer.CommitMemory(size);
}
void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor)
void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, bool depth_read)
{
GSTexture12* vkRt = static_cast<GSTexture12*>(rt);
GSTexture12* vkDs = static_cast<GSTexture12*>(ds);
pxAssert(vkRt || vkDs);
if (m_current_render_target != vkRt || m_current_depth_target != vkDs)
if (m_current_render_target != vkRt || m_current_depth_target != vkDs || m_current_depth_read_only != depth_read)
{
// framebuffer change
EndRenderPass();
@@ -2211,13 +2214,14 @@ void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector
m_current_render_target = vkRt;
m_current_depth_target = vkDs;
m_current_depth_read_only = depth_read;
if (!InRenderPass())
{
if (vkRt)
vkRt->TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
if (vkDs)
vkDs->TransitionToState(D3D12_RESOURCE_STATE_DEPTH_WRITE);
vkDs->TransitionToState(depth_read ? (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) : D3D12_RESOURCE_STATE_DEPTH_WRITE);
}
// This is used to set/initialize the framebuffer for tfx rendering.
@@ -3207,7 +3211,7 @@ void GSDevice12::SetStencilRef(u8 ref)
m_dirty_flags |= DIRTY_FLAG_STENCIL_REF;
}
void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state)
void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback)
{
D3D12DescriptorHandle handle;
if (sr)
@@ -3225,7 +3229,7 @@ void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state)
dtex->TransitionToState(D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
dtex->SetUseFenceCounter(GetCurrentFenceValue());
handle = dtex->GetSRVDescriptor();
handle = feedback ? dtex->GetFBLDescriptor() : dtex->GetSRVDescriptor();
}
else
{
@@ -3312,7 +3316,7 @@ void GSDevice12::UnbindTexture(GSTexture12* tex)
{
for (u32 i = 0; i < NUM_TOTAL_TFX_TEXTURES; i++)
{
if (m_tfx_textures[i] == tex->GetSRVDescriptor())
if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor())
{
m_tfx_textures[i] = m_null_texture->GetSRVDescriptor();
m_dirty_flags |= DIRTY_FLAG_TFX_TEXTURES;
@@ -3327,6 +3331,7 @@ void GSDevice12::UnbindTexture(GSTexture12* tex)
{
EndRenderPass();
m_current_depth_target = nullptr;
m_current_depth_read_only = false;
}
}
@@ -3427,6 +3432,9 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
m_dirty_flags &= ~DIRTY_FLAG_RENDER_TARGET;
m_in_render_pass = true;
if (stencil_end == D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD)
GL_INS("D3D12: BeginRenderPass() end stencil is DISCARDED.");
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt = {};
if (m_current_render_target)
{
@@ -3444,7 +3452,7 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
D3D12_RENDER_PASS_DEPTH_STENCIL_DESC ds = {};
if (m_current_depth_target)
{
ds.cpuDescriptor = m_current_depth_target->GetWriteDescriptor();
ds.cpuDescriptor = m_current_depth_read_only ? m_current_depth_target->GetReadDepthViewDescriptor() : m_current_depth_target->GetWriteDescriptor();
ds.DepthEndingAccess.Type = depth_end;
ds.DepthBeginningAccess.Type = depth_begin;
if (depth_begin == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR)
@@ -3464,7 +3472,8 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
}
GetCommandList()->BeginRenderPass(m_current_render_target ? 1 : 0,
m_current_render_target ? &rt : nullptr, m_current_depth_target ? &ds : nullptr, D3D12_RENDER_PASS_FLAG_NONE);
m_current_render_target ? &rt : nullptr, m_current_depth_target ? &ds : nullptr,
(m_current_depth_target && m_current_depth_read_only) ? (D3D12_RENDER_PASS_FLAG_BIND_READ_ONLY_DEPTH) : D3D12_RENDER_PASS_FLAG_NONE);
}
void GSDevice12::EndRenderPass()
@@ -3546,11 +3555,13 @@ __ri void GSDevice12::ApplyBaseState(u32 flags, ID3D12GraphicsCommandList* cmdli
if (m_current_render_target)
{
cmdlist->OMSetRenderTargets(1, &m_current_render_target->GetWriteDescriptor().cpu_handle, FALSE,
m_current_depth_target ? &m_current_depth_target->GetWriteDescriptor().cpu_handle : nullptr);
m_current_depth_target ?
(m_current_depth_read_only ? &m_current_depth_target->GetReadDepthViewDescriptor().cpu_handle : &m_current_depth_target->GetWriteDescriptor().cpu_handle) :
nullptr);
}
else if (m_current_depth_target)
{
cmdlist->OMSetRenderTargets(0, nullptr, FALSE, &m_current_depth_target->GetWriteDescriptor().cpu_handle);
cmdlist->OMSetRenderTargets(0, nullptr, FALSE, m_current_depth_read_only ? &m_current_depth_target->GetReadDepthViewDescriptor().cpu_handle : &m_current_depth_target->GetWriteDescriptor().cpu_handle);
}
}
}
@@ -3823,7 +3834,20 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// TODO: Backport from vk.
if (stencil_DATE_One)
{
config.ps.date = 0;
config.alpha_second_pass.ps.date = 0;
if (!config.ps.IsFeedbackLoop())
{
config.require_one_barrier = false;
config.require_full_barrier = false;
}
if (!config.alpha_second_pass.ps.IsFeedbackLoop())
{
config.alpha_second_pass.require_one_barrier = false;
config.alpha_second_pass.require_full_barrier = false;
}
}
GSTexture12* colclip_rt = static_cast<GSTexture12*>(g_gs_device->GetColorClipTexture());
GSTexture12* draw_rt = static_cast<GSTexture12*>(config.rt);
@@ -3838,6 +3862,18 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// figure out the pipeline
UpdateHWPipelineSelector(config);
// Handle RT hazard when no barrier was requested
if (m_features.texture_barrier && config.tex && (config.tex == config.rt) && !(config.require_one_barrier || config.require_full_barrier))
{
g_perfmon.Put(GSPerfMon::Barriers, 1);
EndRenderPass();
// Specify null for the after resource as both resources are used after the barrier.
D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_ALIASING, D3D12_RESOURCE_BARRIER_FLAG_NONE};
barrier.Aliasing = {draw_rt->GetResource(), nullptr};
GetCommandList()->ResourceBarrier(1, &barrier);
}
// now blit the colclip texture back to the original target
if (colclip_rt)
{
@@ -3893,15 +3929,6 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
if (config.blend.constant_enable)
SetBlendConstants(config.blend.constant);
// Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy.
if (config.tex && config.tex == config.ds)
{
EndRenderPass();
// Transition dsv as read only.
draw_ds->TransitionToState(D3D12_RESOURCE_STATE_DEPTH_READ);
}
// Primitive ID tracking DATE setup.
GSTexture12* date_image = nullptr;
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
@@ -3952,7 +3979,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
}
// we're not drawing to the RT, so we can use it as a source
if (config.require_one_barrier && !m_features.multidraw_fb_copy)
if (config.require_one_barrier && !m_features.texture_barrier)
PSSetShaderResource(2, draw_rt, true);
}
@@ -3982,16 +4009,29 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
m_pipeline_selector.ds = true;
}
if (draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy) || (config.tex && config.tex == config.rt)))
const bool feedback = draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier) || (config.tex && config.tex == config.rt));
if (feedback && !m_features.texture_barrier)
{
// Requires a copy of the RT.
// Used as "bind rt" flag when texture barrier is unsupported for tex is fb.
draw_rt_clone = static_cast<GSTexture12*>(CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true));
if (!draw_rt_clone)
if (draw_rt_clone)
{
GL_PUSH("D3D12: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
EndRenderPass();
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone, true);
}
else
Console.Warning("D3D12: Failed to allocate temp texture for RT copy.");
}
OMSetRenderTargets(draw_rt, draw_ds, config.scissor);
// For depth testing and sampling, use a read only dsv, otherwise use a write dsv
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, config.tex && config.tex == config.ds);
// Begin render pass if new target or out of the area.
if (!m_in_render_pass)
@@ -4008,7 +4048,8 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
draw_ds ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE : D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
stencil_DATE ? D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE :
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS,
stencil_DATE ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD :
stencil_DATE ? (feedback ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE :
D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD) :
D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
clear_color, draw_ds ? draw_ds->GetClearDepth() : 0.0f, 1);
}
@@ -4036,7 +4077,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
UploadHWDrawVerticesAndIndices(config);
// now we can do the actual draw
SendHWDraw(pipe, config, draw_rt_clone, draw_rt, config.require_one_barrier, config.require_full_barrier, false);
SendHWDraw(pipe, config, draw_rt, feedback, config.require_one_barrier, config.require_full_barrier);
// blend second pass
if (config.blend_multi_pass.enable)
@@ -4066,15 +4107,15 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
pipe.cms = config.alpha_second_pass.colormask;
pipe.dss = config.alpha_second_pass.depth;
pipe.bs = config.blend;
SendHWDraw(pipe, config, draw_rt_clone, draw_rt, config.alpha_second_pass.require_one_barrier, config.alpha_second_pass.require_full_barrier, true);
SendHWDraw(pipe, config, draw_rt, feedback, config.alpha_second_pass.require_one_barrier, config.alpha_second_pass.require_full_barrier);
}
if (draw_rt_clone)
Recycle(draw_rt_clone);
if (date_image)
Recycle(date_image);
if (draw_rt_clone)
Recycle(draw_rt_clone);
// now blit the colclip texture back to the original target
if (colclip_rt)
{
@@ -4109,43 +4150,45 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
}
}
void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt_clone, GSTexture12* draw_rt, const bool one_barrier, const bool full_barrier, const bool skip_first_barrier)
void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt, const bool feedback, const bool one_barrier, const bool full_barrier)
{
if (draw_rt_clone)
if (BindDrawPipeline(pipe) && !m_features.texture_barrier) [[unlikely]]
{
DrawIndexedPrimitive();
return;
}
if (feedback)
{
#ifdef PCSX2_DEVBUILD
if ((one_barrier || full_barrier) && !config.ps.IsFeedbackLoop()) [[unlikely]]
Console.Warning("D3D12: Possible unnecessary copy detected.");
Console.Warning("D3D12: Possible unnecessary barrier detected.");
#endif
auto CopyAndBind = [&](GSVector4i drawarea) {
EndRenderPass();
if (one_barrier || full_barrier)
PSSetShaderResource(2, draw_rt, false, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt, false, true);
CopyRect(draw_rt, draw_rt_clone, drawarea, drawarea.left, drawarea.top);
draw_rt->TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
if (one_barrier || full_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone, true);
};
if (m_features.multidraw_fb_copy && full_barrier)
if (full_barrier)
{
pxAssert(config.drawlist && !config.drawlist->empty());
const u32 draw_list_size = static_cast<u32>(config.drawlist->size());
const u32 indices_per_prim = config.indices_per_prim;
pxAssert(config.drawlist && !config.drawlist->empty());
pxAssert(config.drawlist_bbox && static_cast<u32>(config.drawlist_bbox->size()) == draw_list_size);
GL_PUSH("Split the draw");
g_perfmon.Put(GSPerfMon::Barriers, draw_list_size);
for (u32 n = 0, p = 0; n < draw_list_size; n++)
{
const u32 count = (*config.drawlist)[n] * indices_per_prim;
GSVector4i bbox = (*config.drawlist_bbox)[n].rintersect(config.drawarea);
EndRenderPass();
// Specify null for the after resource as both resources are used after the barrier.
// While this may also be true before the barrier, we only write using the main resource.
D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_ALIASING, D3D12_RESOURCE_BARRIER_FLAG_NONE};
barrier.Aliasing = {draw_rt->GetResource(), nullptr};
GetCommandList()->ResourceBarrier(1, &barrier);
// Copy only the part needed by the draw.
CopyAndBind(bbox);
if (BindDrawPipeline(pipe))
DrawIndexedPrimitive(p, count);
p += count;
@@ -4154,10 +4197,16 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig&
return;
}
if (one_barrier)
{
g_perfmon.Put(GSPerfMon::Barriers, 1);
// Optimization: For alpha second pass we can reuse the copy snapshot from the first pass.
if (!skip_first_barrier)
CopyAndBind(config.drawarea);
EndRenderPass();
// Specify null for the after resource as both resources are used after the barrier.
D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_ALIASING, D3D12_RESOURCE_BARRIER_FLAG_NONE};
barrier.Aliasing = {draw_rt->GetResource(), nullptr};
GetCommandList()->ResourceBarrier(1, &barrier);
}
}
if (BindDrawPipeline(pipe))

View File

@@ -456,17 +456,17 @@ public:
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
void IASetIndexBuffer(const void* index, size_t count);
void PSSetShaderResource(int i, GSTexture* sr, bool check_state);
void PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback = false);
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor);
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, bool depth_read = false);
void SetVSConstantBuffer(const GSHWDrawConfig::VSConstantBuffer& cb);
void SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb);
bool BindDrawPipeline(const PipelineSelector& p);
void RenderHW(GSHWDrawConfig& config) override;
void SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt_clone, GSTexture12* draw_rt, const bool one_barrier, const bool full_barrier, const bool skip_first_barrier);
void SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt, const bool feedback, const bool one_barrier, const bool full_barrier);
void UpdateHWPipelineSelector(GSHWDrawConfig& config);
void UploadHWDrawVerticesAndIndices(const GSHWDrawConfig& config);
@@ -580,6 +580,7 @@ private:
GSTexture12* m_current_render_target = nullptr;
GSTexture12* m_current_depth_target = nullptr;
bool m_current_depth_read_only = false;
D3D12_VIEWPORT m_viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
GSVector4i m_scissor = GSVector4i::zero();

View File

@@ -15,14 +15,19 @@
#include "D3D12MemAlloc.h"
GSTexture12::GSTexture12(Type type, Format format, int width, int height, int levels, DXGI_FORMAT dxgi_format,
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<D3D12MA::Allocation> allocation,
const D3D12DescriptorHandle& srv_descriptor, const D3D12DescriptorHandle& write_descriptor,
const D3D12DescriptorHandle& uav_descriptor, WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<ID3D12Resource> resource_fbl,
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& ro_dsv_descriptor,
const D3D12DescriptorHandle& uav_descriptor, const D3D12DescriptorHandle& fbl_descriptor,
WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
: m_resource(std::move(resource))
, m_resource_fbl(std::move(resource_fbl))
, m_allocation(std::move(allocation))
, m_srv_descriptor(srv_descriptor)
, m_write_descriptor(write_descriptor)
, m_read_dsv_descriptor(ro_dsv_descriptor)
, m_uav_descriptor(uav_descriptor)
, m_fbl_descriptor(fbl_descriptor)
, m_write_descriptor_type(wdtype)
, m_dxgi_format(dxgi_format)
, m_resource_state(resource_state)
@@ -55,6 +60,7 @@ void GSTexture12::Destroy(bool defer)
break;
case WriteDescriptorType::DSV:
dev->DeferDescriptorDestruction(dev->GetDSVHeapManager(), &m_write_descriptor);
dev->DeferDescriptorDestruction(dev->GetDSVHeapManager(), &m_read_dsv_descriptor);
break;
case WriteDescriptorType::None:
default:
@@ -64,8 +70,13 @@ void GSTexture12::Destroy(bool defer)
if (m_uav_descriptor)
dev->DeferDescriptorDestruction(dev->GetDescriptorHeapManager(), &m_uav_descriptor);
if (m_fbl_descriptor)
dev->DeferDescriptorDestruction(dev->GetDescriptorHeapManager(), &m_fbl_descriptor);
dev->DeferResourceDestruction(m_allocation.get(), m_resource.get());
dev->DeferResourceDestruction(m_allocation.get(), m_resource_fbl.get());
m_resource.reset();
m_resource_fbl.reset();
m_allocation.reset();
}
else
@@ -79,6 +90,7 @@ void GSTexture12::Destroy(bool defer)
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&m_write_descriptor);
dev->GetDSVHeapManager().Free(&m_read_dsv_descriptor);
break;
case WriteDescriptorType::None:
default:
@@ -88,7 +100,11 @@ void GSTexture12::Destroy(bool defer)
if (m_uav_descriptor)
dev->GetDescriptorHeapManager().Free(&m_uav_descriptor);
if (m_fbl_descriptor)
dev->GetDescriptorHeapManager().Free(&m_fbl_descriptor);
m_resource.reset();
m_resource_fbl.reset();
m_allocation.reset();
}
@@ -135,7 +151,9 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
// RT's tend to be larger, so we'll keep them committed for speed.
pxAssert(levels == 1);
allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
allocationDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS;
desc.Layout = D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE;
optimized_clear_value.Format = rtv_format;
state = D3D12_RESOURCE_STATE_RENDER_TARGET;
}
@@ -167,20 +185,63 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
wil::com_ptr_nothrow<ID3D12Resource> resource;
wil::com_ptr_nothrow<ID3D12Resource> resource_fbl;
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation;
HRESULT hr = dev->GetAllocator()->CreateResource(&allocationDesc, &desc, state,
(type == Type::RenderTarget || type == Type::DepthStencil) ? &optimized_clear_value : nullptr, allocation.put(),
IID_PPV_ARGS(resource.put()));
if (FAILED(hr))
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
Console.Error("Create texture failed: 0x%08X", hr);
return {};
if (type == Type::RenderTarget)
{
const D3D12_RESOURCE_ALLOCATION_INFO allocInfo = dev->GetDevice()->GetResourceAllocationInfo(0, 1, &desc);
HRESULT hr = dev->GetAllocator()->AllocateMemory(&allocationDesc, &allocInfo, allocation.put());
if (FAILED(hr))
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
Console.Error("Allocate texture memory failed: 0x%08X", hr);
return {};
}
hr = dev->GetAllocator()->CreateAliasingResource(allocation.get(), 0, &desc, state,
(type == Type::RenderTarget || type == Type::DepthStencil) ? &optimized_clear_value : nullptr,
IID_PPV_ARGS(resource.put()));
if (FAILED(hr))
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
Console.Error("Create texture resource 1 failed: 0x%08X", hr);
return {};
}
hr = dev->GetAllocator()->CreateAliasingResource(allocation.get(), 0, &desc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
(type == Type::RenderTarget || type == Type::DepthStencil) ? &optimized_clear_value : nullptr,
IID_PPV_ARGS(resource_fbl.put()));
if (FAILED(hr))
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
Console.Error("Create texture resource 2 failed: 0x%08X", hr);
return {};
}
}
else
{
HRESULT hr = dev->GetAllocator()->CreateResource(&allocationDesc, &desc, state,
(type == Type::RenderTarget || type == Type::DepthStencil) ? &optimized_clear_value : nullptr, allocation.put(),
IID_PPV_ARGS(resource.put()));
if (FAILED(hr))
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
Console.Error("Create texture failed: 0x%08X", hr);
return {};
}
}
D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor;
D3D12DescriptorHandle srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor, fbl_descriptor;
WriteDescriptorType write_descriptor_type = WriteDescriptorType::None;
if (srv_format != DXGI_FORMAT_UNKNOWN)
{
@@ -195,7 +256,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
write_descriptor_type = WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.get(), rtv_format, &write_descriptor))
{
dev->GetRTVHeapManager().Free(&srv_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -204,9 +265,15 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
case Type::DepthStencil:
{
write_descriptor_type = WriteDescriptorType::DSV;
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor))
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor, false))
{
dev->GetDSVHeapManager().Free(&srv_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
if (!CreateDSVDescriptor(resource.get(), dsv_format, &ro_dsv_descriptor, true))
{
dev->GetDSVHeapManager().Free(&write_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -218,14 +285,47 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.get(), dsv_format, &uav_descriptor))
{
dev->GetDescriptorHeapManager().Free(&write_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
dev->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&ro_dsv_descriptor);
dev->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
if (resource_fbl)
{
if (!CreateSRVDescriptor(resource_fbl.get(), levels, srv_format, &fbl_descriptor))
{
dev->GetDescriptorHeapManager().Free(&uav_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
dev->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&ro_dsv_descriptor);
dev->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
return std::unique_ptr<GSTexture12>(
new GSTexture12(type, format, width, height, levels, dxgi_format, std::move(resource), std::move(allocation),
srv_descriptor, write_descriptor, uav_descriptor, write_descriptor_type, state));
new GSTexture12(type, format, width, height, levels, dxgi_format, std::move(resource), std::move(resource_fbl), std::move(allocation),
srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor, fbl_descriptor, write_descriptor_type, state));
}
std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resource> resource, Type type, Format format,
@@ -234,7 +334,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
{
const D3D12_RESOURCE_DESC desc = resource->GetDesc();
D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor;
D3D12DescriptorHandle srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor;
WriteDescriptorType write_descriptor_type = WriteDescriptorType::None;
if (srv_format != DXGI_FORMAT_UNKNOWN)
{
@@ -247,16 +347,22 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
write_descriptor_type = WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.get(), rtv_format, &write_descriptor))
{
GSDevice12::GetInstance()->GetRTVHeapManager().Free(&srv_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
else if (type == Type::DepthStencil)
{
write_descriptor_type = WriteDescriptorType::DSV;
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor))
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor, false))
{
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&srv_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
if (!CreateDSVDescriptor(resource.get(), dsv_format, &ro_dsv_descriptor, true))
{
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&write_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -265,15 +371,27 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
{
if (!CreateUAVDescriptor(resource.get(), srv_format, &uav_descriptor))
{
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&write_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
GSDevice12::GetInstance()->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&ro_dsv_descriptor);
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
return std::unique_ptr<GSTexture12>(new GSTexture12(type, format, static_cast<u32>(desc.Width), desc.Height,
desc.MipLevels, desc.Format, std::move(resource), {}, srv_descriptor, write_descriptor, uav_descriptor,
write_descriptor_type, resource_state));
desc.MipLevels, desc.Format, std::move(resource), {}, {}, srv_descriptor, write_descriptor, {}, uav_descriptor,
{}, write_descriptor_type, resource_state));
}
bool GSTexture12::CreateSRVDescriptor(
@@ -297,7 +415,7 @@ bool GSTexture12::CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT form
{
if (!GSDevice12::GetInstance()->GetRTVHeapManager().Allocate(dh))
{
Console.Error("Failed to allocate SRV descriptor");
Console.Error("Failed to allocate RTV descriptor");
return false;
}
@@ -306,15 +424,15 @@ bool GSTexture12::CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT form
return true;
}
bool GSTexture12::CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh)
bool GSTexture12::CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh, bool read_only)
{
if (!GSDevice12::GetInstance()->GetDSVHeapManager().Allocate(dh))
{
Console.Error("Failed to allocate SRV descriptor");
Console.Error("Failed to allocate DSV descriptor");
return false;
}
const D3D12_DEPTH_STENCIL_VIEW_DESC desc = {format, D3D12_DSV_DIMENSION_TEXTURE2D, D3D12_DSV_FLAG_NONE};
const D3D12_DEPTH_STENCIL_VIEW_DESC desc = {format, D3D12_DSV_DIMENSION_TEXTURE2D, read_only ? D3D12_DSV_FLAG_READ_ONLY_DEPTH : D3D12_DSV_FLAG_NONE };
GSDevice12::GetInstance()->GetDevice()->CreateDepthStencilView(resource, &desc, dh->cpu_handle);
return true;
}
@@ -609,7 +727,36 @@ void GSTexture12::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RE
if (m_resource_state == state)
return;
TransitionSubresourceToState(cmdlist, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, m_resource_state, state);
// Read only depth requires special handling as we might want to write stencil.
// Also batch the transition barriers as per recommendation from docs.
if (state == (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
// Transition to read depth/write stencil
const D3D12_RESOURCE_BARRIER barriers[2] = {
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 0, m_resource_state, (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)}}},
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, m_resource_state, D3D12_RESOURCE_STATE_DEPTH_WRITE}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(m_resource_state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else if (m_resource_state == (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
// Transition from read depth/write stencil
const D3D12_RESOURCE_BARRIER barriers[2] = {
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 0, (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE), state}}},
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, D3D12_RESOURCE_STATE_DEPTH_WRITE, state}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else
{
// Normal transition
TransitionSubresourceToState(cmdlist, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, m_resource_state, state);
}
m_resource_state = state;
}

View File

@@ -30,10 +30,13 @@ public:
__fi const D3D12DescriptorHandle& GetSRVDescriptor() const { return m_srv_descriptor; }
__fi const D3D12DescriptorHandle& GetWriteDescriptor() const { return m_write_descriptor; }
__fi const D3D12DescriptorHandle& GetReadDepthViewDescriptor() const { return m_read_dsv_descriptor; }
__fi const D3D12DescriptorHandle& GetUAVDescriptor() const { return m_uav_descriptor; }
__fi const D3D12DescriptorHandle& GetFBLDescriptor() const { return m_fbl_descriptor; }
__fi D3D12_RESOURCE_STATES GetResourceState() const { return m_resource_state; }
__fi DXGI_FORMAT GetDXGIFormat() const { return m_dxgi_format; }
__fi ID3D12Resource* GetResource() const { return m_resource.get(); }
__fi ID3D12Resource* GetFBLResource() const { return m_resource_fbl.get(); }
void* GetNativeHandle() const override;
@@ -68,14 +71,16 @@ private:
};
GSTexture12(Type type, Format format, int width, int height, int levels, DXGI_FORMAT dxgi_format,
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<D3D12MA::Allocation> allocation,
const D3D12DescriptorHandle& srv_descriptor, const D3D12DescriptorHandle& write_descriptor,
const D3D12DescriptorHandle& uav_descriptor, WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state);
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<ID3D12Resource> resource_fbl,
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& ro_dsv_descriptor,
const D3D12DescriptorHandle& uav_descriptor, const D3D12DescriptorHandle& fbl_descriptor,
WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state);
static bool CreateSRVDescriptor(
ID3D12Resource* resource, u32 levels, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh, bool read_only);
static bool CreateUAVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
ID3D12GraphicsCommandList* GetCommandBufferForUpdate();
@@ -83,11 +88,14 @@ private:
void CopyTextureDataForUpload(void* dst, const void* src, u32 pitch, u32 upload_pitch, u32 height) const;
wil::com_ptr_nothrow<ID3D12Resource> m_resource;
wil::com_ptr_nothrow<ID3D12Resource> m_resource_fbl;
wil::com_ptr_nothrow<D3D12MA::Allocation> m_allocation;
D3D12DescriptorHandle m_srv_descriptor = {};
D3D12DescriptorHandle m_write_descriptor = {};
D3D12DescriptorHandle m_read_dsv_descriptor = {};
D3D12DescriptorHandle m_uav_descriptor = {};
D3D12DescriptorHandle m_fbl_descriptor = {};
WriteDescriptorType m_write_descriptor_type = WriteDescriptorType::None;
DXGI_FORMAT m_dxgi_format = DXGI_FORMAT_UNKNOWN;

View File

@@ -7085,7 +7085,10 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
if (m_texture_shuffle)
{
// We can't do tex is FB if the source and destination aren't pointing to the same bit of texture.
if (m_texture_shuffle && (floor(abs(m_vt.m_min.t.y) + tex->m_region.GetMinY()) != floor(abs(m_vt.m_min.p.y))))
if (floor(abs(m_vt.m_min.t.y) + tex->m_region.GetMinY()) != floor(abs(m_vt.m_min.p.y)))
return false;
if (abs(floor(abs(m_vt.m_min.t.x) + tex->m_region.GetMinX()) - floor(abs(m_vt.m_min.p.x))) > 16)
return false;
GL_CACHE("HW: Enabling tex-is-fb for texture shuffle.");
@@ -8034,6 +8037,14 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale));
m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor;
// ComputeDrawlistGetSize expects the original index layout, so needs to be called before we modify it via HandleProvokingVertexFirst/SetupIA.
if (m_conf.require_full_barrier && (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
{
ComputeDrawlistGetSize(rt->m_scale);
m_conf.drawlist = &m_drawlist;
m_conf.drawlist_bbox = &m_drawlist_bbox;
}
HandleProvokingVertexFirst();
SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0);
@@ -8122,13 +8133,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.alpha_second_pass.enable = false;
}
if (m_conf.require_full_barrier && (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
{
ComputeDrawlistGetSize(rt->m_scale);
m_conf.drawlist = &m_drawlist;
m_conf.drawlist_bbox = &m_drawlist_bbox;
}
if (!m_channel_shuffle_width)
g_gs_device->RenderHW(m_conf);
else

View File

@@ -2430,6 +2430,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize();
GSTexture* primid_texture = nullptr;
GSTexture* draw_rt_clone = nullptr;
GSTexture* colclip_rt = g_gs_device->GetColorClipTexture();
if (colclip_rt)
@@ -2505,23 +2506,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
break;
}
GSTexture* draw_rt_clone = nullptr;
if (config.require_one_barrier && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true);
if (draw_rt_clone)
{
GL_PUSH("GL: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(colclip_rt ? colclip_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
}
else
Console.Warning("GL: Failed to allocate temp texture for RT copy.");
}
IASetVertexBuffer(config.verts, config.nverts, GetVertexAlignment(config.vs.expand));
m_vertex.start *= GetExpansionFactor(config.vs.expand);
@@ -2550,9 +2534,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
PSSetShaderResource(0, config.tex);
if (config.pal)
PSSetShaderResource(1, config.pal);
if (draw_rt_clone)
PSSetShaderResource(2, draw_rt_clone);
else if (config.require_one_barrier || config.require_full_barrier)
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier))
PSSetShaderResource(2, colclip_rt ? colclip_rt : config.rt);
SetupSampler(config.sampler);
@@ -2668,6 +2650,25 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
glTextureBarrier();
}
if (draw_rt && (config.require_one_barrier || (config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true);
if (draw_rt_clone)
{
GL_PUSH("GL: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone);
}
else
Console.Warning("GL: Failed to allocate temp texture for RT copy.");
}
OMSetRenderTargets(draw_rt, draw_ds, &config.scissor);
OMSetColorMaskState(config.colormask);
SetupOM(config.depth);

View File

@@ -206,8 +206,18 @@ bool GSDeviceVK::SelectInstanceExtensions(ExtensionList* extension_list, const W
if (enable_debug_utils && !SupportsExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false))
Console.Warning("VK: Debug report requested, but extension is not available.");
oe->vk_ext_swapchain_maintenance1 = (wi.type != WindowInfo::Type::Surfaceless &&
SupportsExtension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false));
oe->vk_swapchain_maintenance1 = wi.type != WindowInfo::Type::Surfaceless;
if (wi.type != WindowInfo::Type::Surfaceless)
{
oe->vk_swapchain_maintenance1 = true;
// VK_EXT_swapchain_maintenance1 requires VK_EXT_surface_maintenance1.
// VK_KHR_swapchain_maintenance1 might require VK_KHR_surface_maintenance1 (It does on Nvidia).
// If either VK_KHR_surface_maintenance1 is supported, or VK_EXT_swapchain_maintenance1 is unsupported, don't try VK_EXT_swapchain_maintenance1.
oe->vk_swapchain_maintenance1_is_khr = SupportsExtension(VK_KHR_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false) ||
!SupportsExtension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false);
}
else
oe->vk_swapchain_maintenance1 = false;
// Needed for exclusive fullscreen control.
SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false);
@@ -411,6 +421,18 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab
m_optional_extensions.vk_ext_line_rasterization = SupportsExtension(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, false);
m_optional_extensions.vk_khr_driver_properties = SupportsExtension(VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, false);
if (m_optional_extensions.vk_swapchain_maintenance1)
{
const bool khr_swapchain_maintenance1 = SupportsExtension(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false);
// vk_swapchain_maintenance1_is_khr will be set if we havn't enabled VK_EXT_surface_maintenance1
// This will happen if either the VK_EXT_surface_maintenance1 was unsupported, or we instead found the KHR version.
// As the EXT version depends on the surface maintenance1 extension, we need to check that aswell.
m_optional_extensions.vk_swapchain_maintenance1 = khr_swapchain_maintenance1 ? khr_swapchain_maintenance1 :
(!m_optional_extensions.vk_swapchain_maintenance1_is_khr && SupportsExtension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false));
m_optional_extensions.vk_swapchain_maintenance1_is_khr = khr_swapchain_maintenance1;
}
// glslang generates debug info instructions before phi nodes at the beginning of blocks when non-semantic debug info
// is enabled, triggering errors by spirv-val. Gate it by an environment variable if you want source debugging until
// this is fixed.
@@ -420,10 +442,6 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab
SupportsExtension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, false);
}
m_optional_extensions.vk_ext_swapchain_maintenance1 =
m_optional_extensions.vk_ext_swapchain_maintenance1 &&
SupportsExtension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false);
#ifdef _WIN32
m_optional_extensions.vk_ext_full_screen_exclusive =
enable_surface && SupportsExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, false);
@@ -611,8 +629,10 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachment_feedback_loop_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT};
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = {
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_ext_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT};
VkPhysicalDeviceSwapchainMaintenance1FeaturesKHR swapchain_maintenance1_khr_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR};
if (m_optional_extensions.vk_ext_provoking_vertex)
{
@@ -634,10 +654,18 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer
attachment_feedback_loop_feature.attachmentFeedbackLoopLayout = VK_TRUE;
Vulkan::AddPointerToChain(&device_info, &attachment_feedback_loop_feature);
}
if (m_optional_extensions.vk_ext_swapchain_maintenance1)
if (m_optional_extensions.vk_swapchain_maintenance1)
{
swapchain_maintenance1_feature.swapchainMaintenance1 = VK_TRUE;
Vulkan::AddPointerToChain(&device_info, &swapchain_maintenance1_feature);
if (m_optional_extensions.vk_swapchain_maintenance1_is_khr)
{
swapchain_maintenance1_khr_feature.swapchainMaintenance1 = VK_TRUE;
Vulkan::AddPointerToChain(&device_info, &swapchain_maintenance1_khr_feature);
}
else
{
swapchain_maintenance1_ext_feature.swapchainMaintenance1 = VK_TRUE;
Vulkan::AddPointerToChain(&device_info, &swapchain_maintenance1_ext_feature);
}
}
VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device);
@@ -704,8 +732,10 @@ bool GSDeviceVK::ProcessDeviceExtensions()
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT};
VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT};
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = {
VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_ext_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, nullptr, VK_TRUE};
VkPhysicalDeviceSwapchainMaintenance1FeaturesKHR swapchain_maintenance1_khr_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_KHR, nullptr, VK_TRUE};
VkPhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT attachment_feedback_loop_feature = {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_FEATURES_EXT};
@@ -718,8 +748,10 @@ bool GSDeviceVK::ProcessDeviceExtensions()
Vulkan::AddPointerToChain(&features2, &rasterization_order_access_feature);
if (m_optional_extensions.vk_ext_attachment_feedback_loop_layout)
Vulkan::AddPointerToChain(&features2, &attachment_feedback_loop_feature);
if (m_optional_extensions.vk_ext_swapchain_maintenance1)
Vulkan::AddPointerToChain(&features2, &swapchain_maintenance1_feature);
if (m_optional_extensions.vk_swapchain_maintenance1 && m_optional_extensions.vk_swapchain_maintenance1_is_khr)
Vulkan::AddPointerToChain(&features2, &swapchain_maintenance1_khr_feature);
if (m_optional_extensions.vk_swapchain_maintenance1 && !m_optional_extensions.vk_swapchain_maintenance1_is_khr)
Vulkan::AddPointerToChain(&features2, &swapchain_maintenance1_ext_feature);
// query
vkGetPhysicalDeviceFeatures2(m_physical_device, &features2);
@@ -794,8 +826,9 @@ bool GSDeviceVK::ProcessDeviceExtensions()
m_optional_extensions.vk_ext_calibrated_timestamps = false;
}
m_optional_extensions.vk_ext_swapchain_maintenance1 &=
(swapchain_maintenance1_feature.swapchainMaintenance1 == VK_TRUE);
m_optional_extensions.vk_swapchain_maintenance1 &= m_optional_extensions.vk_swapchain_maintenance1_is_khr ?
(swapchain_maintenance1_khr_feature.swapchainMaintenance1 == VK_TRUE) :
(swapchain_maintenance1_ext_feature.swapchainMaintenance1 == VK_TRUE);
Console.WriteLn(
"VK_EXT_provoking_vertex is %s", m_optional_extensions.vk_ext_provoking_vertex ? "supported" : "NOT supported");
@@ -805,8 +838,9 @@ bool GSDeviceVK::ProcessDeviceExtensions()
m_optional_extensions.vk_ext_calibrated_timestamps ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_rasterization_order_attachment_access is %s",
m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_swapchain_maintenance1 is %s",
m_optional_extensions.vk_ext_swapchain_maintenance1 ? "supported" : "NOT supported");
Console.WriteLn("VK_%s_swapchain_maintenance1 is %s",
m_optional_extensions.vk_swapchain_maintenance1_is_khr ? "KHR" : "EXT",
m_optional_extensions.vk_swapchain_maintenance1 ? "supported" : "NOT supported");
Console.WriteLn("VK_EXT_full_screen_exclusive is %s",
m_optional_extensions.vk_ext_full_screen_exclusive ? "supported" : "NOT supported");
Console.WriteLn("VK_KHR_driver_properties is %s",
@@ -1266,7 +1300,8 @@ void GSDeviceVK::SubmitCommandBuffer(VKSwapChain* present_swap_chain)
{
// VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain.
if (res == VK_ERROR_OUT_OF_DATE_KHR)
ResizeWindow(0, 0, m_window_info.surface_scale);
// Defer until next frame, otherwise resizing would invalidate swapchain before next present.
m_resize_requested = true;
else
LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
@@ -2183,6 +2218,8 @@ bool GSDeviceVK::UpdateWindow()
void GSDeviceVK::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale)
{
m_resize_requested = false;
if (!m_swap_chain || (m_swap_chain->GetWidth() == static_cast<u32>(new_window_width) &&
m_swap_chain->GetHeight() == static_cast<u32>(new_window_height)))
{
@@ -2290,10 +2327,9 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip)
return PresentResult::FrameSkipped;
}
VkResult res = m_swap_chain->AcquireNextImage();
VkResult res = m_resize_requested ? VK_ERROR_OUT_OF_DATE_KHR : m_swap_chain->AcquireNextImage();
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
m_swap_chain->ReleaseCurrentImage();
if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR)
@@ -2313,6 +2349,8 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip)
res = m_swap_chain->AcquireNextImage();
}
else
LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR() failed: ");
// This can happen when multiple resize events happen in quick succession.
// In this case, just wait until the next frame to try again.
@@ -5545,7 +5583,6 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config)
void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
{
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize());
GSTextureVK* draw_rt = static_cast<GSTextureVK*>(config.rt);
GSTextureVK* draw_ds = static_cast<GSTextureVK*>(config.ds);
@@ -5678,24 +5715,6 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
break;
}
if (config.require_one_barrier && !m_features.texture_barrier)
{
// requires a copy of the RT
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true));
if (draw_rt_clone)
{
EndRenderPass();
GL_PUSH("VK: Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
else
Console.Warning("VK: Failed to allocate temp texture for RT copy.");
}
// Switch to colclip target for colclip hw rendering
if (pipe.ps.colclip_hw)
{
@@ -5773,6 +5792,26 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
pipe.feedback_loop_flags |= m_current_framebuffer_feedback_loop;
}
if (draw_rt && (config.require_one_barrier || (config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true));
if (draw_rt_clone)
{
GL_PUSH("VK: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
EndRenderPass();
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone, true);
}
else
Console.Warning("VK: Failed to allocate temp texture for RT copy.");
}
// We don't need the very first barrier if this is the first draw after switching to feedback loop,
// because the layout change in itself enforces the execution dependency. colclip hw needs a barrier between
// setup and the first draw to read it. TODO: Make colclip hw use subpasses instead.

View File

@@ -41,7 +41,8 @@ public:
bool vk_ext_rasterization_order_attachment_access : 1;
bool vk_ext_full_screen_exclusive : 1;
bool vk_ext_line_rasterization : 1;
bool vk_ext_swapchain_maintenance1 : 1;
bool vk_swapchain_maintenance1 : 1;
bool vk_swapchain_maintenance1_is_khr : 1;
bool vk_khr_driver_properties : 1;
bool vk_khr_shader_non_semantic_info : 1;
bool vk_ext_attachment_feedback_loop_layout : 1;
@@ -367,6 +368,7 @@ public:
private:
std::unique_ptr<VKSwapChain> m_swap_chain;
bool m_resize_requested = false;
VkDescriptorSetLayout m_utility_ds_layout = VK_NULL_HANDLE;
VkPipelineLayout m_utility_pipeline_layout = VK_NULL_HANDLE;

View File

@@ -241,4 +241,7 @@ VULKAN_DEVICE_ENTRY_POINT(vkCmdPushDescriptorSetKHR, false)
// VK_EXT_swapchain_maintenance1
VULKAN_DEVICE_ENTRY_POINT(vkReleaseSwapchainImagesEXT, false)
// VK_KHR_swapchain_maintenance1
VULKAN_DEVICE_ENTRY_POINT(vkReleaseSwapchainImagesKHR, false)
#endif // VULKAN_DEVICE_ENTRY_POINT

View File

@@ -366,7 +366,17 @@ bool VKSwapChain::CreateSwapChain()
// Store the old/current swap chain when recreating for resize
// Old swap chain is destroyed regardless of whether the create call succeeds
VkSwapchainKHR old_swap_chain = m_swap_chain;
VkSwapchainKHR old_swap_chain;
// RDNA4 experences a 2s delay in the following 2-3 vkAcquireNextImageKHR calls if we pass the old swapchain to the new one.
// Instead, pass null. This requires us to have freed the old image, which we already do with the swapchain maintenance extension.
if (GSDeviceVK::GetInstance()->IsDeviceAMD() && GSDeviceVK::GetInstance()->GetOptionalExtensions().vk_swapchain_maintenance1)
{
vkDestroySwapchainKHR(GSDeviceVK::GetInstance()->GetDevice(), m_swap_chain, nullptr);
old_swap_chain = VK_NULL_HANDLE;
}
else
old_swap_chain = m_swap_chain;
m_swap_chain = VK_NULL_HANDLE;
// Now we can actually create the swap chain
@@ -549,17 +559,29 @@ void VKSwapChain::ReleaseCurrentImage()
return;
if ((m_image_acquire_result.value() == VK_SUCCESS || m_image_acquire_result.value() == VK_SUBOPTIMAL_KHR) &&
GSDeviceVK::GetInstance()->GetOptionalExtensions().vk_ext_swapchain_maintenance1)
GSDeviceVK::GetInstance()->GetOptionalExtensions().vk_swapchain_maintenance1)
{
GSDeviceVK::GetInstance()->WaitForGPUIdle();
const VkReleaseSwapchainImagesInfoEXT info = {.sType = VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_EXT,
.swapchain = m_swap_chain,
.imageIndexCount = 1,
.pImageIndices = &m_current_image};
VkResult res = vkReleaseSwapchainImagesEXT(GSDeviceVK::GetInstance()->GetDevice(), &info);
VkResult res;
if (GSDeviceVK::GetInstance()->GetOptionalExtensions().vk_swapchain_maintenance1_is_khr)
{
const VkReleaseSwapchainImagesInfoKHR info = {.sType = VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_KHR,
.swapchain = m_swap_chain,
.imageIndexCount = 1,
.pImageIndices = &m_current_image};
res = vkReleaseSwapchainImagesKHR(GSDeviceVK::GetInstance()->GetDevice(), &info);
}
else
{
const VkReleaseSwapchainImagesInfoEXT info = {.sType = VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_EXT,
.swapchain = m_swap_chain,
.imageIndexCount = 1,
.pImageIndices = &m_current_image};
res = vkReleaseSwapchainImagesEXT(GSDeviceVK::GetInstance()->GetDevice(), &info);
}
if (res != VK_SUCCESS)
LOG_VULKAN_ERROR(res, "vkReleaseSwapchainImagesEXT() failed: ");
LOG_VULKAN_ERROR(res, "vkReleaseSwapchainImages() failed: ");
}
m_image_acquire_result.reset();

View File

@@ -106,14 +106,15 @@ static void HotkeyLoadStateSlot(s32 slot)
Error error;
if (!VMManager::LoadStateFromSlot(slot, false, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
});
}
static void HotkeySaveStateSlot(s32 slot)
{
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
}
static bool CanPause()

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
#include <ctime>
#include <string>
#include <memory>
#include <optional>
struct Pcsx2Config;
@@ -26,6 +27,8 @@ namespace FullscreenUI
void OpenPauseMenu();
bool OpenAchievementsWindow();
bool OpenLeaderboardsWindow();
void ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup);
void ReportStateSaveError(const std::string& message, std::optional<s32> slot);
// NOTE: Only call from GS thread.
bool IsAchievementsWindowOpen();

View File

@@ -2396,6 +2396,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
const float width = LayoutScale(600.0f);
const float title_height = g_large_font.second + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f;
@@ -2464,7 +2465,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
is_open = false;
}
ImGui::PopStyleColor(3);
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(3);
ImGui::PopFont();
@@ -2523,7 +2524,7 @@ void ImGuiFullscreen::DrawInputDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIBackgroundColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true;
if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open,
@@ -2715,6 +2716,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true;
const u32 flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
@@ -2745,7 +2747,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::EndPopup();
}
ImGui::PopStyleColor(3);
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(4);
ImGui::PopFont();

View File

@@ -1382,8 +1382,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
Error error;
if (!VMManager::LoadStateFromSlot(slot, false, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
});
Close();
}
@@ -1393,8 +1392,7 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
Error error;
if (!VMManager::LoadStateFromSlot(slot, true, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, true);
});
Close();
}
@@ -1402,7 +1400,9 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
void SaveStateSelectorUI::SaveCurrentSlot()
{
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
});
Close();
}

View File

@@ -765,9 +765,9 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_
{
shown_prompt = true;
Host::ReportInfoAsync(TRANSLATE("SDLInputSource", "SDL3 Migration"),
TRANSLATE("SDLInputSource", "As part of our upgrade to SDL3, we've had to migrate your binds\n"
"Your controller did not match the Xbox layout and may need rebinding\n"
"Please verify your controller settings and amend if required"));
TRANSLATE("SDLInputSource", "As part of our upgrade to SDL3, we've had to migrate your binds.\n"
"Your controller did not match the Xbox layout and may need rebinding.\n"
"Please verify your controller settings and amend if required."));
// Also apply BPM setting for legacy binds
// We assume this is a Nintendo controller, BPM will check if it is

View File

@@ -6,6 +6,7 @@
#include "Host.h"
#include "Memory.h"
#include "Elfheader.h"
#include "SaveState.h"
#include "PINE.h"
#include "VMManager.h"
#include "common/Error.h"
@@ -19,7 +20,6 @@
#include <thread>
#include "fmt/format.h"
#include "IconsFontAwesome6.h"
#if defined(_WIN32)
#define read_portable(a, b, c) (recv(a, (char*)b, c, 0))
@@ -646,7 +646,11 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
goto error;
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
goto error;
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] { VMManager::SaveStateToSlot(slot); });
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
SaveState_ReportSaveErrorOSD(error, slot);
});
});
buf_cnt += 1;
break;
}
@@ -659,8 +663,7 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
Error state_error;
if (!VMManager::LoadStateFromSlot(slot, false, &state_error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
state_error.GetDescription(), Host::OSD_INFO_DURATION);
SaveState_ReportLoadErrorOSD(state_error.GetDescription(), slot, false);
});
buf_cnt += 1;
break;

View File

@@ -51,7 +51,7 @@ namespace Patch
BYTES_T
};
static constexpr std::array<const char*, 3> s_place_to_string = {{"0", "1", "2"}};
static constexpr std::array<const char*, 4> s_place_to_string = {{"0", "1", "2", "3"}};
static constexpr std::array<const char*, 2> s_cpu_to_string = {{"EE", "IOP"}};
static constexpr std::array<const char*, 9> s_type_to_string = {
{"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble", "bytes"}};
@@ -125,10 +125,6 @@ namespace Patch
void (*func)(PatchGroup* group, const std::string_view cmd, const std::string_view param);
};
using PatchList = std::vector<PatchGroup>;
using ActivePatchList = std::vector<const PatchCommand*>;
using EnablePatchList = std::vector<std::string>;
namespace PatchFunc
{
static void patch(PatchGroup* group, const std::string_view cmd, const std::string_view param);
@@ -141,23 +137,23 @@ namespace Patch
static int PatchTableExecute(PatchGroup* group, const std::string_view lhs, const std::string_view rhs,
const std::span<const PatchTextTable>& Table);
static void LoadPatchLine(PatchGroup* group, const std::string_view line);
static u32 LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file);
static u32 LoadPatchesFromString(std::vector<PatchGroup>* patch_list, const std::string& patch_file);
static bool OpenPatchesZip();
static std::string GetPnachTemplate(
const std::string_view serial, u32 crc, bool include_serial, bool add_wildcard, bool all_crcs);
static std::vector<std::string> FindPatchFilesOnDisk(
const std::string_view serial, u32 crc, bool cheats, bool all_crcs);
static bool ContainsPatchName(const PatchInfoList& patches, const std::string_view patchName);
static bool ContainsPatchName(const PatchList& patches, const std::string_view patchName);
static bool ContainsPatchName(const std::vector<PatchInfo>& patches, const std::string_view patchName);
static bool ContainsPatchName(const std::vector<PatchGroup>& patches, const std::string_view patchName);
template <typename F>
static void EnumeratePnachFiles(const std::string_view serial, u32 crc, bool cheats, bool for_ui, const F& f);
static bool PatchStringHasUnlabelledPatch(const std::string& pnach_data);
static void ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches);
static void ExtractPatchInfo(std::vector<PatchInfo>* dst, const std::string& pnach_data, u32* num_unlabelled_patches);
static void ReloadEnabledLists();
static u32 EnablePatches(const PatchList& patches, const EnablePatchList& enable_list, const EnablePatchList& enable_immediately_list);
static u32 EnablePatches(const std::vector<PatchGroup>* patches, const std::vector<std::string>& enable_list, const std::vector<std::string>* enable_immediately_list);
static void ApplyPatch(const PatchCommand* p);
static void ApplyDynaPatch(const DynamicPatch& patch, u32 address);
@@ -175,21 +171,21 @@ namespace Patch
const char* PATCH_DISABLE_CONFIG_KEY = "Disable";
static zip_t* s_patches_zip;
static PatchList s_gamedb_patches;
static PatchList s_game_patches;
static PatchList s_cheat_patches;
static std::vector<PatchGroup> s_gamedb_patches;
static std::vector<PatchGroup> s_game_patches;
static std::vector<PatchGroup> s_cheat_patches;
static u32 s_gamedb_counts = 0;
static u32 s_patches_counts = 0;
static u32 s_cheats_counts = 0;
static ActivePatchList s_active_patches;
static std::vector<const PatchCommand*> s_active_patches;
static std::vector<DynamicPatch> s_active_gamedb_dynamic_patches;
static std::vector<DynamicPatch> s_active_pnach_dynamic_patches;
static EnablePatchList s_enabled_cheats;
static EnablePatchList s_enabled_patches;
static EnablePatchList s_just_enabled_cheats;
static EnablePatchList s_just_enabled_patches;
static std::vector<std::string> s_enabled_cheats;
static std::vector<std::string> s_enabled_patches;
static std::vector<std::string> s_just_enabled_cheats;
static std::vector<std::string> s_just_enabled_patches;
static u32 s_patches_crc;
static std::optional<float> s_override_aspect_ratio;
static std::optional<GSInterlaceMode> s_override_interlace_mode;
@@ -218,7 +214,7 @@ void Patch::TrimPatchLine(std::string& buffer)
buffer.erase(pos);
}
bool Patch::ContainsPatchName(const PatchList& patch_list, const std::string_view patch_name)
bool Patch::ContainsPatchName(const std::vector<PatchGroup>& patch_list, const std::string_view patch_name)
{
return std::find_if(patch_list.begin(), patch_list.end(), [&patch_name](const PatchGroup& patch) {
return patch.name == patch_name;
@@ -253,7 +249,7 @@ void Patch::LoadPatchLine(PatchGroup* group, const std::string_view line)
PatchTableExecute(group, key, value, s_patch_commands);
}
u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file)
u32 Patch::LoadPatchesFromString(std::vector<PatchGroup>* patch_list, const std::string& patch_file)
{
const size_t before = patch_list->size();
@@ -264,7 +260,7 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch
// Ungrouped/legacy patches should merge with other ungrouped patches.
if (current_patch_group.name.empty())
{
const PatchList::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(),
const std::vector<PatchGroup>::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(),
[](const PatchGroup& pg) { return pg.name.empty(); });
if (ungrouped_patch != patch_list->end())
{
@@ -407,7 +403,7 @@ std::vector<std::string> Patch::FindPatchFilesOnDisk(const std::string_view seri
return ret;
}
bool Patch::ContainsPatchName(const PatchInfoList& patches, const std::string_view patchName)
bool Patch::ContainsPatchName(const std::vector<PatchInfo>& patches, const std::string_view patchName)
{
return std::find_if(patches.begin(), patches.end(), [&patchName](const PatchInfo& patch) {
return patch.name == patchName;
@@ -491,11 +487,15 @@ bool Patch::PatchStringHasUnlabelledPatch(const std::string& pnach_data)
return false;
}
void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches)
void Patch::ExtractPatchInfo(std::vector<PatchInfo>* dst, const std::string& pnach_data, u32* num_unlabelled_patches)
{
std::istringstream ss(pnach_data);
std::string line;
PatchInfo current_patch;
std::optional<patch_place_type> last_place;
bool unknown_place = false;
while (std::getline(ss, line))
{
TrimPatchLine(line);
@@ -522,6 +522,8 @@ void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data,
}
}
current_patch = {};
last_place = std::nullopt;
unknown_place = false;
}
current_patch.name = line.substr(1, line.length() - 2);
@@ -534,13 +536,52 @@ void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data,
// Just ignore other directives, who knows what rubbish people have in here.
// Use comment for description if it hasn't been otherwise specified.
if (key == "author")
{
current_patch.author = value;
}
else if (key == "description")
{
current_patch.description = value;
}
else if (key == "comment" && current_patch.description.empty())
{
current_patch.description = value;
else if (key == "patch" && !has_patch && num_unlabelled_patches)
(*num_unlabelled_patches)++;
}
else if (key == "patch")
{
if (!has_patch && num_unlabelled_patches)
(*num_unlabelled_patches)++;
// Try to extract the place value of the patch lines so we can
// display it in the GUI if they all match. TODO: Don't duplicate
// all this parsing logic twice.
if (unknown_place)
continue;
std::string::size_type comma_pos = value.find(",");
if (comma_pos == std::string::npos)
comma_pos = 0;
const std::string_view padded_place = value.substr(0, comma_pos);
const std::string_view place_string = StringUtil::StripWhitespace(padded_place);
const std::optional<patch_place_type> place = LookupEnumName<patch_place_type>(
place_string, s_place_to_string);
if (!place.has_value() || (last_place.has_value() && place != last_place))
{
// This group contains patch lines with different or invalid
// place values.
current_patch.place = std::nullopt;
unknown_place = true;
continue;
}
current_patch.place = place;
last_place = place;
}
else if (key == "dpatch")
{
current_patch.place = std::nullopt;
unknown_place = true;
}
}
// Last one.
@@ -570,9 +611,9 @@ std::string_view Patch::PatchInfo::GetNameParentPart() const
return ret;
}
Patch::PatchInfoList Patch::GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches)
std::vector<Patch::PatchInfo> Patch::GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches)
{
PatchInfoList ret;
std::vector<PatchInfo> ret;
if (num_unlabelled_patches)
*num_unlabelled_patches = 0;
@@ -592,14 +633,14 @@ std::string Patch::GetPnachFilename(const std::string_view serial, u32 crc, bool
void Patch::ReloadEnabledLists()
{
const EnablePatchList prev_enabled_cheats = std::move(s_enabled_cheats);
const std::vector<std::string> prev_enabled_cheats = std::move(s_enabled_cheats);
if (EmuConfig.EnableCheats && !Achievements::IsHardcoreModeActive())
s_enabled_cheats = Host::GetStringListSetting(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY);
else
s_enabled_cheats = {};
const EnablePatchList prev_enabled_patches = std::exchange(s_enabled_patches, Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY));
const EnablePatchList disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY);
const std::vector<std::string> prev_enabled_patches = std::exchange(s_enabled_patches, Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY));
const std::vector<std::string> disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY);
// Name based matching for widescreen/NI settings.
if (EmuConfig.EnableWideScreenPatches)
@@ -649,12 +690,10 @@ void Patch::ReloadEnabledLists()
}
}
u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable_list, const EnablePatchList& enable_immediately_list)
u32 Patch::EnablePatches(const std::vector<PatchGroup>* patches, const std::vector<std::string>& enable_list, const std::vector<std::string>* enable_immediately_list)
{
ActivePatchList patches_to_apply_immediately;
u32 count = 0;
for (const PatchGroup& p : patches)
for (const PatchGroup& p : *patches)
{
// For compatibility, we auto enable anything that's not labelled.
// Also for gamedb patches.
@@ -664,7 +703,6 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
Console.WriteLn(Color_Green, fmt::format("Enabled patch: {}",
p.name.empty() ? std::string_view("<unknown>") : std::string_view(p.name)));
const bool apply_immediately = std::find(enable_immediately_list.begin(), enable_immediately_list.end(), p.name) != enable_immediately_list.end();
for (const PatchCommand& ip : p.patches)
{
// print the actual patch lines only in verbose mode (even in devel)
@@ -672,8 +710,6 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
DevCon.WriteLnFmt(" {}", ip.ToString());
s_active_patches.push_back(&ip);
if (apply_immediately && ip.placetopatch == PPT_ONCE_ON_LOAD)
patches_to_apply_immediately.push_back(&ip);
}
for (const DynamicPatch& dp : p.dpatches)
@@ -690,12 +726,28 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
count += p.name.empty() ? (static_cast<u32>(p.patches.size()) + static_cast<u32>(p.dpatches.size())) : 1;
}
if (!patches_to_apply_immediately.empty())
// Apply PPT_ON_LOAD_OR_WHEN_ENABLED patches immediately.
if (enable_immediately_list && !enable_immediately_list->empty())
{
Host::RunOnCPUThread([patches = std::move(patches_to_apply_immediately)]() {
for (const PatchCommand* i : patches)
// Don't pass pointers to patch objects themselves here just in case the
// patches are reloaded twice in a row before this event makes it.
Host::RunOnCPUThread([patches, enable_immediately_list]() {
for (const PatchGroup& group : *patches)
{
ApplyPatch(i);
const bool apply_immediately = std::find(
enable_immediately_list->begin(),
enable_immediately_list->end(),
group.name) != enable_immediately_list->end();
if (!apply_immediately)
continue;
for (const PatchCommand& command : group.patches)
{
if (command.placetopatch != PPT_ON_LOAD_OR_WHEN_ENABLED)
continue;
ApplyPatch(&command);
}
}
});
}
@@ -762,19 +814,23 @@ void Patch::UpdateActivePatches(bool reload_enabled_list, bool verbose, bool ver
u32 gp_count = 0;
if (EmuConfig.EnablePatches)
{
gp_count = EnablePatches(s_gamedb_patches, EnablePatchList(), EnablePatchList());
gp_count = EnablePatches(&s_gamedb_patches, std::vector<std::string>(), nullptr);
s_gamedb_counts = gp_count;
if (gp_count > 0)
message.append(TRANSLATE_PLURAL_STR("Patch", "%n GameDB patches are active.", "OSD Message", gp_count));
}
const u32 p_count = EnablePatches(s_game_patches, s_enabled_patches, apply_new_patches ? s_just_enabled_patches : EnablePatchList());
const u32 p_count = EnablePatches(
&s_game_patches, s_enabled_patches, apply_new_patches ? &s_just_enabled_patches : nullptr);
s_patches_counts = p_count;
if (p_count > 0)
message.append_format("{}{}", message.empty() ? "" : "\n",
TRANSLATE_PLURAL_STR("Patch", "%n game patches are active.", "OSD Message", p_count));
const u32 c_count = EmuConfig.EnableCheats ? EnablePatches(s_cheat_patches, s_enabled_cheats, apply_new_patches ? s_just_enabled_cheats : EnablePatchList()) : 0;
u32 c_count = 0;
if (EmuConfig.EnableCheats)
c_count = EnablePatches(
&s_cheat_patches, s_enabled_cheats, apply_new_patches ? &s_just_enabled_cheats : nullptr);
s_cheats_counts = c_count;
if (c_count > 0)
message.append_format("{}{}", message.empty() ? "" : "\n",
@@ -892,7 +948,7 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view cmd, cons
if (!placetopatch.has_value())
{
PATCH_ERROR("Invalid 'place' value '{}' (0 - once on startup, 1: continuously)", pieces[0]);
PATCH_ERROR("Invalid 'place' value '{}' (0: on boot only, 1: continuously, 2: on boot and continuously, 3: on boot and when enabled in the GUI)", pieces[0]);
return;
}
if (!addr.has_value() || !addr_end.empty())
@@ -1083,6 +1139,19 @@ void Patch::PatchFunc::dpatch(PatchGroup* group, const std::string_view cmd, con
group->dpatches.push_back(dpatch);
}
void Patch::ApplyBootPatches()
{
ApplyLoadedPatches(PPT_ONCE_ON_LOAD);
ApplyLoadedPatches(PPT_COMBINED_0_1);
ApplyLoadedPatches(PPT_ON_LOAD_OR_WHEN_ENABLED);
}
void Patch::ApplyVsyncPatches()
{
ApplyLoadedPatches(PPT_CONTINUOUSLY);
ApplyLoadedPatches(PPT_COMBINED_0_1);
}
// This is for applying patches directly to memory
void Patch::ApplyLoadedPatches(patch_place_type place)
{
@@ -1731,3 +1800,31 @@ void Patch::ApplyDynaPatch(const DynamicPatch& patch, u32 address)
memWrite32(address + replacement.offset, replacement.value);
}
}
const char* Patch::PlaceToString(std::optional<patch_place_type> place)
{
if (!place.has_value())
//: Time when a patch is applied.
return TRANSLATE("Patch", "Unknown");
switch (*place)
{
case Patch::PPT_ONCE_ON_LOAD:
//: Time when a patch is applied.
return TRANSLATE("Patch", "Only On Startup");
case Patch::PPT_CONTINUOUSLY:
//: Time when a patch is applied.
return TRANSLATE("Patch", "Every Frame");
case Patch::PPT_COMBINED_0_1:
//: Time when a patch is applied.
return TRANSLATE("Patch", "On Startup & Every Frame");
case Patch::PPT_ON_LOAD_OR_WHEN_ENABLED:
//: Time when a patch is applied.
return TRANSLATE("Patch", "On Startup & When Enabled");
default:
{
}
}
return "";
}

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