Compare commits

..

52 Commits

Author SHA1 Message Date
SilentHeII
0f82503cf7 GameDB: Added various memory card filters
Reads Twisted Metal Black for bonus unlockable.

Adds memcardFilters for Shadow Hearts: Covenant

Ignore my previous pull request. I have no idea why it formatted it so obnoxiously.

Anyway, you get extra ingame items if you have a save file from Shadow Hearts 1.

GameIndex changes

Added memcardFilters for Biohazard Outbreak & Biohazard Outbreak File 2 to allow for creation of network configurations.

Added missing titles

Added missing titles to allow games to load their own saves.
2026-01-06 20:56:26 +01:00
Christopher Obbard
33f625a4e2 cmake: only require Qt modules when Qt UI is enabled
The Qt6 CorePrivate/GuiPrivate/WidgetsPrivate components are only needed
for the Qt UI build. Move their find_package() call under ENABLE_QT_UI
to avoid requiring private Qt modules when building without the UI.

Signed-off-by: Christopher Obbard <obbardc@gmail.com>
2026-01-06 20:44:35 +01:00
JordanTheToaster
5b0c22c343 Mac: Update MoltenVK to v1.4.1 2026-01-06 20:37:29 +01:00
Florin9doi
ea963ffd72 USB: Train Mascon and Master Controller emulation 2026-01-06 20:18:15 +01:00
lightningterror
bd9dcbe441 GS/D3D: Default to DX12 on older GCN amd cards. 2026-01-06 20:09:04 +01:00
PCSX2 Bot
2a1f29c641 [ci skip] Qt: Update Base Translation. 2026-01-06 20:08:18 +01:00
TheLastRar
38883e8df4 GS/DX12: Use cmdlist parameter for read depth transitions 2026-01-06 20:08:02 +01:00
PCSX2 Bot
f971040912 [ci skip] PAD: Update to latest controller database. 2026-01-06 20:07:43 +01:00
Jordan
9aac7e8426 [ci skip] GameDB: CMR 2005 Fixes (#13782)
hotpatch!!!
2026-01-05 17:44:44 -05:00
Ty Lamontagne
96284205a1 [ci skip] Release 2.6 2026-01-04 14:20:16 -05:00
RedPanda4552
4a1d9d31d0 Achievements: Store token to separate ini 2026-01-04 13:56:14 -05:00
Ty
12d6087f2a Translations: Syncing Crowdin translations 2026-01-04 13:55:43 -05:00
Ty
251962c415 Qt: Don't show the dev name if it equals the displayname 2026-01-04 13:49:18 -05:00
Ty
1bdd7d2352 FSUI: Don't show the name if it equals the displayname when listing 2026-01-04 13:49:18 -05:00
SternXD
7b98259ea1 FullscreenUI: Update auto mapping to include device names with display names 2026-01-03 23:59:57 -05:00
Ty
ee8166d1fe Debugger: Check PC instead of cycles when stopping for a breakpoint 2026-01-03 10:14:21 -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
108 changed files with 206574 additions and 155033 deletions

View File

@@ -4,6 +4,17 @@ on:
schedule:
- cron: "0 0 * * *" # Every day at 12am UTC.
workflow_dispatch: # As well as manually.
inputs:
stableBuild:
description: 'Build stable version'
required: false
type: boolean
default: false
publish:
description: 'Publish to Flathub'
required: false
type: boolean
default: true
jobs:
@@ -52,8 +63,8 @@ jobs:
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: true
publish: ${{ inputs.publish || true }}
fetchTags: true
stableBuild: false
stableBuild: ${{ inputs.stableBuild || false }}
secrets: inherit

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

@@ -12,7 +12,7 @@ on:
- master
workflow_dispatch:
inputs:
is_prelease:
is_prerelease:
description: 'Should be a pre-release?'
required: true
default: 'true'
@@ -73,7 +73,7 @@ jobs:
with:
body_path: ./release-notes.md
draft: true
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prelease == 'true' }}
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prerelease == 'true' }}
tag_name: ${{ steps.tag_version.outputs.new_tag }}
- name: Create a GitHub Release (Push)
@@ -100,7 +100,7 @@ jobs:
cmakeflags: ""
buildAppImage: true
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
secrets: inherit
build_linux_flatpak:
@@ -114,9 +114,9 @@ jobs:
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: false
publish: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }} # prerelease builds are published by the cron job
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ inputs.is_prerelease == 'false' }}
secrets: inherit
# Windows
@@ -133,7 +133,7 @@ jobs:
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
secrets: inherit
# MacOS
@@ -147,7 +147,7 @@ jobs:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prerelease == 'false' }}
sign_and_notarize: true
secrets: inherit
@@ -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

@@ -47,7 +47,7 @@ LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
MOLTENVK=1.4.1
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
@@ -88,7 +88,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
@@ -277,7 +277,7 @@ rm -fr "MoltenVK-${MOLTENVK}"
tar xf "v$MOLTENVK.tar.gz"
cd "MoltenVK-${MOLTENVK}"
./fetchDependencies --macos
make macos
make macos MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 MVK_CONFIG_USE_METAL_PRIVATE_API=1
cp Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$INSTALLDIR/lib/"
cd ..

View File

@@ -29,7 +29,7 @@ LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
MOLTENVK=1.4.1
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
@@ -69,7 +69,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
@@ -225,7 +225,7 @@ cd "MoltenVK-${MOLTENVK}"
sed -i '' 's/xcodebuild "$@"/xcodebuild $XCODEBUILD_EXTRA_ARGS "$@"/g' fetchDependencies
sed -i '' 's/XCODEBUILD :=/XCODEBUILD ?=/g' Makefile
XCODEBUILD_EXTRA_ARGS="VALID_ARCHS=x86_64" ./fetchDependencies --macos
XCODEBUILD="set -o pipefail && xcodebuild VALID_ARCHS=x86_64" make macos
XCODEBUILD="set -o pipefail && xcodebuild VALID_ARCHS=x86_64" make macos MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0 MVK_CONFIG_USE_METAL_PRIVATE_API=1
cp Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib "$INSTALLDIR/lib/"
cd ..

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

@@ -11384,6 +11384,10 @@ SCUS-97197:
name: "War of the Monsters"
region: "NTSC-U"
compat: 5
memcardFilters: # Reads Twisted Metal Black for bonus unlockable.
- "SCUS-97101"
- "SCUS-97179"
- "SCUS-97197"
SCUS-97198:
name: "Sly Cooper and the Thievius Raccoonus"
region: "NTSC-U"
@@ -13722,6 +13726,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 +14002,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 +16678,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 +17834,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 +19959,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 +20131,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"
@@ -20882,10 +20898,8 @@ SLES-52636:
gameFixes:
- FullVU0SyncHack # Fixes in-game timer.
gsHWFixes:
recommendedBlendingLevel: 3 # Fixes missing lighting and car reflections.
halfPixelOffset: 1 # Fixes 4 split lines in stage intros.
autoFlush: 1 # Fixes incorrect colors.
alignSprite: 1 # Fixes vertical lines such as in FMVs.
minimumBlendingLevel: 3 # Fixes missing lighting and car reflections.
halfPixelOffset: 4 # Fixes lines in game and FMVs.
SLES-52637:
name: "TOCA Race Driver 2"
region: "PAL-M5"
@@ -22269,6 +22283,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 +22607,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 +25066,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 +30145,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"
@@ -30564,6 +30587,11 @@ SLES-82030:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLES-82030"
- "SLES-82031"
- "SLES-50677"
- "SLES-50822"
SLES-82031:
name: "Shadow Hearts - Covenant [Disc 2 of 2]"
region: "PAL-M3"
@@ -30571,8 +30599,11 @@ SLES-82031:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLES-82030"
- "SLES-82031"
- "SLES-50677"
- "SLES-50822"
SLES-82032:
name: "Metal Gear Solid 3 - Snake Eater"
region: "PAL-G"
@@ -32394,6 +32425,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 ~파리는 불타고 있는가~"
@@ -43453,6 +43485,11 @@ SLPM-65428:
name-en: "BioHazard Outbreak"
region: "NTSC-J"
compat: 5
memcardFilters:
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-65429:
name: "ギャラクシーエンジェル Moonlit Lovers [初回限定版ファーストパッケージ]"
name-sort: "ぎゃらくしーえんじぇる Moonlit Lovers [しょかいげんていばんふぁーすとぱっけーじ]"
@@ -44953,6 +44990,8 @@ SLPM-65692:
- "SLPM-65692"
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-65693:
name: "ときめきメモリアル3 ~約束のあの場所で~ [コナミ殿堂セレクション]"
name-sort: "ときめきめもりある3 やくそくのあのばしょで [こなみでんどうせれくしょん]"
@@ -48603,6 +48642,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: "新・豪血寺一族 -煩悩解放-"
@@ -53659,6 +53699,11 @@ SLPM-74201:
name-sort: "ばいおはざーど あうとぶれいく [PlayStation2 the Best]"
name-en: "BioHazard Outbreak [PlayStation2 the Best]"
region: "NTSC-J"
memcardFilters:
- "SLPM-65428"
- "SLPM-74201"
- "SLPM-65286"
- "BWNETCNF"
SLPM-74202:
name: "風雲 新撰組 [PlayStation2 the Best]"
name-sort: "ふううん しんせんぐみ [PlayStation2 the Best]"
@@ -58824,6 +58869,11 @@ SLPS-25317:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25317"
- "SLPS-25318"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25318:
name: "シャドウハーツⅡ [DXパック] [ディスク2/2]"
name-sort: "しゃどうはーつ2 [DXぱっく] [でぃすく2/2]"
@@ -58833,8 +58883,11 @@ SLPS-25318:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25317"
- "SLPS-25318"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25319:
name: "ケロケロキング スーパーDX"
name-sort: "けろけろきんぐ すーぱーDX"
@@ -58913,6 +58966,11 @@ SLPS-25334:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25334"
- "SLPS-25335"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25335:
name: "シャドウハーツⅡ [通常版] [ディスク2/2]"
name-sort: "しゃどうはーつ2 [つうじょうばん] [でぃすく2/2]"
@@ -58922,8 +58980,11 @@ SLPS-25335:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-25334"
- "SLPS-25335"
- "SLPS-25041"
- "SLPS-73418"
SLPS-25336:
name: "バスランディング3 [Sammy best] [つりコン2+ 同梱版]"
name-sort: "ばすらんでぃんぐ3 [Sammy best] [つりこん2 どうこんばん]"
@@ -63221,6 +63282,11 @@ SLPS-73214:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-73214"
- "SLPS-73215"
- "SLPS-25041"
- "SLPS-73418"
SLPS-73215:
name: "シャドウハーツⅡ ディレクターズカット [PlayStation2 the Best] [ディスク2/2]"
name-sort: "しゃどうはーつ2 でぃれくたーずかっと [PlayStation2 the Best] [でぃすく2/2]"
@@ -63230,8 +63296,11 @@ SLPS-73215:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLPS-73214"
- "SLPS-73215"
- "SLPS-25041"
- "SLPS-73418"
SLPS-73216:
name: "マグナカルタ [PlayStation2 the Best]"
name-sort: "まぐなかるた [PlayStation2 the Best]"
@@ -66745,6 +66814,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 +67039,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 +68298,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"
@@ -69295,6 +69369,10 @@ SLUS-21041:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLUS-21041"
- "SLUS-21044"
- "SLUS-20347"
SLUS-21042:
name: "Darkwatch"
region: "NTSC-U"
@@ -69313,8 +69391,10 @@ SLUS-21044:
halfPixelOffset: 5 # Fixes shadow positioning.
autoFlush: 2 # Makes the shadow monsters appear.
nativeScaling: 2 # Aligns post processing and bloom.
memcardFilters:
memcardFilters: # Reads Shadow Hearts for extra items.
- "SLUS-21041"
- "SLUS-21044"
- "SLUS-20347"
SLUS-21045:
name: "Conflict - Vietnam"
region: "NTSC-U"
@@ -74172,8 +74252,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 +75053,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 +75414,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,
@@ -245,7 +245,8 @@
03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows,
030000008f0e00001411000000000000,Gamo2 Divaller,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:Windows,
03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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:Windows,
03000000c9110000f055000000000000,GC100XF,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:Windows,
03000000c21100000791000000000000,Be1 GC101 Controller 1.03,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:Windows,
03000000c9110000f055000000000000,Be1 GC100XF Controller,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:Windows,
030000008305000009a0000000000000,Genius,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:Windows,
030000008305000031b0000000000000,Genius Maxfire Blaze 3,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:Windows,
03000000451300000010000000000000,Genius Maxfire Grandias 12,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:Windows,
@@ -850,6 +851,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 +897,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 +987,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 +996,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 +1397,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 +1597,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 +1708,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 +1864,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

@@ -109,14 +109,14 @@ disable_compiler_warnings_for_target(speex)
# Find the Qt components that we need.
if(ENABLE_QT_UI)
find_package(Qt6 6.10.0 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
endif()
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
find_package(Qt6 COMPONENTS CorePrivate GuiPrivate WidgetsPrivate REQUIRED)
endif()
if (Qt6_VERSION VERSION_GREATER_EQUAL 6.10.0)
find_package(Qt6 COMPONENTS CorePrivate GuiPrivate WidgetsPrivate REQUIRED)
endif()
# The docking system for the debugger.
# The docking system for the debugger.
find_package(KDDockWidgets-qt6 2.3.0 REQUIRED)
endif()
if(WIN32)
add_subdirectory(3rdparty/rainterface EXCLUDE_FROM_ALL)

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

@@ -100,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);
@@ -1008,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)
@@ -1040,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)
@@ -1075,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
@@ -1108,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)
@@ -1180,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)
@@ -2129,7 +2156,7 @@ void MainWindow::onVMResumed()
if (m_display_surface)
{
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
}
}
@@ -2449,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);
@@ -2500,7 +2531,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
updateWindowState();
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
return wi;
}
@@ -2517,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
{
@@ -2528,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
{
@@ -2570,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();
@@ -2608,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())
@@ -2622,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);
}
@@ -2639,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) {
@@ -2677,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
@@ -2708,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())
@@ -2730,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
}
}
@@ -3308,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);
}
@@ -3320,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)
@@ -3328,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();
}
@@ -3342,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()
@@ -3354,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.
@@ -138,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

@@ -86,6 +86,7 @@ namespace QtHost
//////////////////////////////////////////////////////////////////////////
static QTimer* s_settings_save_timer = nullptr;
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
static std::unique_ptr<INISettingsInterface> s_secrets_settings_interface;
static bool s_batch_mode = false;
static bool s_nogui_mode = false;
static bool s_start_big_picture_mode = false;
@@ -1307,6 +1308,7 @@ bool QtHost::InitializeConfig()
// Write crash dumps to the data directory, since that'll be accessible for certain.
CrashHandler::SetWriteDirectory(EmuFolders::DataRoot);
// Load main settings ini
const std::string path = Path::Combine(EmuFolders::Settings, "PCSX2.ini");
const bool settings_exists = FileSystem::FileExists(path.c_str());
Console.WriteLnFmt("Loading config from {}.", path);
@@ -1347,6 +1349,29 @@ bool QtHost::InitializeConfig()
SaveSettings();
}
// Layer secrets ini on top
const std::string secrets_path = Path::Combine(EmuFolders::Settings, "secrets.ini");
const bool secrets_settings_exists = FileSystem::FileExists(secrets_path.c_str());
Console.WriteLnFmt("Loading secrets from {}.", secrets_path);
s_secrets_settings_interface = std::make_unique<INISettingsInterface>(std::move(secrets_path));
Host::Internal::SetSecretsSettingsLayer(s_secrets_settings_interface.get());
if (!secrets_settings_exists || !s_secrets_settings_interface->Load())
{
if (!s_base_settings_interface->Save(&error))
{
QMessageBox::critical(
nullptr, QStringLiteral("PCSX2"),
QStringLiteral(
"Failed to save secrets to\n\n%1\n\nThe error was: %2\n\nPlease ensure this directory is writable. You "
"can also try portable mode by creating portable.txt in the same directory you installed PCSX2 into.")
.arg(QString::fromStdString(s_secrets_settings_interface->GetFileName()))
.arg(QString::fromStdString(error.GetDescription())));
return false;
}
}
// Setup wizard was incomplete last time?
s_run_setup_wizard =
s_run_setup_wizard || s_base_settings_interface->GetBoolValue("UI", "SetupWizardIncomplete", false);

View File

@@ -366,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);
@@ -376,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

@@ -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

@@ -202,7 +202,11 @@ void ControllerBindingWidget::onAutomaticBindingClicked()
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
QAction* action;
if(dev.first.compare(dev.second, Qt::CaseInsensitive) == 0)
action = menu.addAction(dev.first);
else
action = menu.addAction(QStringLiteral("%1: %2").arg(dev.first).arg(dev.second));
action->setData(dev.first);
connect(action, &QAction::triggered, this, [this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;
@@ -1152,7 +1156,11 @@ void USBDeviceWidget::onAutomaticBindingClicked()
for (const QPair<QString, QString>& dev : m_dialog->getDeviceList())
{
// we set it as data, because the device list could get invalidated while the menu is up
QAction* action = menu.addAction(QStringLiteral("%1 (%2)").arg(dev.first).arg(dev.second));
QAction* action;
if(dev.first.compare(dev.second, Qt::CaseInsensitive) == 0)
action = menu.addAction(dev.first);
else
action = menu.addAction(QStringLiteral("%1: %2").arg(dev.first).arg(dev.second));
action->setData(dev.first);
connect(action, &QAction::triggered, this, [this, action]() { doDeviceAutomaticBinding(action->data().toString()); });
added = true;

View File

@@ -88,7 +88,11 @@ ControllerGlobalSettingsWidget::~ControllerGlobalSettingsWidget() = default;
void ControllerGlobalSettingsWidget::addDeviceToList(const QString& identifier, const QString& name)
{
QListWidgetItem* item = new QListWidgetItem();
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
if(identifier.compare(name,Qt::CaseInsensitive) == 0)
item->setText(identifier);
else
item->setText(QStringLiteral("%1: %2").arg(identifier).arg(name));
item->setData(Qt::UserRole, identifier);
m_ui.deviceList->addItem(item);
}

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">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@
#include "common/MD5Digest.h"
#include "common/Path.h"
#include "common/ScopedGuard.h"
#include "common/SettingsInterface.h"
#include "common/SmallString.h"
#include "common/StringUtil.h"
#include "common/Timer.h"
@@ -439,7 +440,25 @@ bool Achievements::Initialize()
IdentifyGame(VMManager::GetDiscCRC(), VMManager::GetCurrentCRC());
const std::string username = Host::GetBaseStringSettingValue("Achievements", "Username");
const std::string api_token = Host::GetBaseStringSettingValue("Achievements", "Token");
// Check the base settings file to see if the token is defined inside. Move if found.
std::string oldToken = Host::GetBaseStringSettingValue("Achievements", "Token");
if (!oldToken.empty())
{
auto secretsLock = Host::GetSecretsSettingsLock();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->SetStringValue("Achievements", "Token", oldToken.c_str());
secretsInterface->Save();
oldToken.clear();
auto baseLock = Host::GetSettingsLock();
SettingsInterface* baseInterface = Host::Internal::GetBaseSettingsLayer();
baseInterface->DeleteValue("Achievements", "Token");
baseInterface->Save();
}
const std::string api_token = Host::GetStringSettingValue("Achievements", "Token");
if (!username.empty() && !api_token.empty())
{
Console.WriteLn("Achievements: Attempting login with user '%s'...", username.c_str());
@@ -1785,9 +1804,12 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error
// Store configuration.
Host::SetBaseStringSettingValue("Achievements", "Username", params->username);
Host::SetBaseStringSettingValue("Achievements", "Token", user->token);
Host::SetBaseStringSettingValue("Achievements", "LoginTimestamp", fmt::format("{}", std::time(nullptr)).c_str());
Host::CommitBaseSettingChanges();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->SetStringValue("Achievements", "Token", user->token);
secretsInterface->Save();
ShowLoginSuccess(client);
}
@@ -1887,9 +1909,13 @@ void Achievements::Logout()
Console.WriteLn("Achievements: Clearing credentials...");
Host::RemoveBaseSettingValue("Achievements", "Username");
Host::RemoveBaseSettingValue("Achievements", "Token");
Host::RemoveBaseSettingValue("Achievements", "LoginTimestamp");
Host::CommitBaseSettingChanges();
auto secretsLock = Host::GetSecretsSettingsLock();
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
secretsInterface->DeleteValue("Achievements", "Token");
secretsInterface->Save();
}

View File

@@ -10,9 +10,7 @@
std::vector<BreakPoint> CBreakPoints::breakPoints_;
u32 CBreakPoints::breakSkipFirstAtEE_ = 0;
u64 CBreakPoints::breakSkipFirstTicksEE_ = 0;
u32 CBreakPoints::breakSkipFirstAtIop_ = 0;
u64 CBreakPoints::breakSkipFirstTicksIop_ = 0;
std::vector<MemCheck> CBreakPoints::memChecks_;
std::vector<MemCheck*> CBreakPoints::cleanupMemChecks_;
bool CBreakPoints::breakpointTriggered_ = false;
@@ -393,20 +391,18 @@ void CBreakPoints::SetSkipFirst(BreakPointCpu cpu, u32 pc)
if (cpu == BREAKPOINT_EE)
{
breakSkipFirstAtEE_ = standardizeBreakpointAddress(pc);
breakSkipFirstTicksEE_ = r5900Debug.getCycles();
}
else if (cpu == BREAKPOINT_IOP)
{
breakSkipFirstAtIop_ = pc;
breakSkipFirstTicksIop_ = r3000Debug.getCycles();
}
}
u32 CBreakPoints::CheckSkipFirst(BreakPointCpu cpu, u32 cmpPc)
{
if (cpu == BREAKPOINT_EE && breakSkipFirstTicksEE_ == r5900Debug.getCycles())
if (cpu == BREAKPOINT_EE && breakSkipFirstAtEE_ == r5900Debug.getPC())
return breakSkipFirstAtEE_;
else if (cpu == BREAKPOINT_IOP && breakSkipFirstTicksIop_ == r3000Debug.getCycles())
else if (cpu == BREAKPOINT_IOP && breakSkipFirstAtIop_ == r3000Debug.getPC())
return breakSkipFirstAtIop_;
return 0;
}
@@ -414,9 +410,7 @@ u32 CBreakPoints::CheckSkipFirst(BreakPointCpu cpu, u32 cmpPc)
void CBreakPoints::ClearSkipFirst()
{
breakSkipFirstAtEE_ = 0;
breakSkipFirstTicksEE_ = 0;
breakSkipFirstAtIop_ = 0;
breakSkipFirstTicksIop_ = 0;
}
const std::vector<MemCheck> CBreakPoints::GetMemCheckRanges()

View File

@@ -439,7 +439,9 @@ GSRendererType D3D::GetPreferredRenderer()
if (!feature_level.has_value())
return GSRendererType::DX11;
else if (feature_level == D3D_FEATURE_LEVEL_12_0)
return check_vulkan_supported() ? GSRendererType::VK : GSRendererType::DX11;
return check_vulkan_supported() ? GSRendererType::VK : GSRendererType::DX12;
else if (feature_level == D3D_FEATURE_LEVEL_11_1)
return GSRendererType::DX12;
else
return GSRendererType::DX11;
}

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;
}
}
@@ -3447,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)
@@ -3467,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()
@@ -3549,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);
}
}
}
@@ -3826,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);
@@ -3841,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)
{
@@ -3896,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)
@@ -3955,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);
}
@@ -3985,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)
@@ -4011,8 +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 ? (draw_rt_clone ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE :
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);
}
@@ -4040,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)
@@ -4070,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)
{
@@ -4113,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;
@@ -4158,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}}},
};
cmdlist->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}}},
};
cmdlist->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

@@ -8037,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);
@@ -8125,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();

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