mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35624a12d9 | ||
|
|
9b147cc57c | ||
|
|
10e13cfece | ||
|
|
7b2eb7bc47 | ||
|
|
ab1cb802d8 | ||
|
|
366cdd8df0 | ||
|
|
bc3cfb1373 | ||
|
|
db6792af2e | ||
|
|
a1485fb7cd | ||
|
|
c72c309218 | ||
|
|
58899a9ed3 | ||
|
|
0823c70460 | ||
|
|
e5c29a3975 | ||
|
|
1174ae99c9 | ||
|
|
e2d3680038 | ||
|
|
8630893cb1 | ||
|
|
53598b970d | ||
|
|
89de00ac36 | ||
|
|
d5ddf07958 | ||
|
|
30dcf4a14a | ||
|
|
a87710e4bc | ||
|
|
a12f87fec2 | ||
|
|
8ba9bba094 | ||
|
|
1363571c14 | ||
|
|
80de666fcc | ||
|
|
ff0a2f84fa | ||
|
|
0676f145bc | ||
|
|
e19ae2bf60 | ||
|
|
7782d930d5 | ||
|
|
d1a53fe29b | ||
|
|
c484cf286c | ||
|
|
f8882c4da6 | ||
|
|
52c17e67a5 | ||
|
|
615cd00147 | ||
|
|
cb0bf953d3 | ||
|
|
26a68ef76a | ||
|
|
e4c1dc2359 | ||
|
|
40425e3bee | ||
|
|
e51e4a35fe | ||
|
|
bd1b9ea718 | ||
|
|
faaa376232 | ||
|
|
e9ca1a6ead | ||
|
|
4d3149eacb | ||
|
|
78822c96fb | ||
|
|
a78617b987 | ||
|
|
3059ab2b12 | ||
|
|
1d0f6cc5b7 | ||
|
|
38a35043a8 | ||
|
|
7385cbe40a | ||
|
|
9955e07470 | ||
|
|
74db386144 | ||
|
|
3f72efeb7a | ||
|
|
d0f8905439 | ||
|
|
5a60259ef5 | ||
|
|
c8dffccaa7 | ||
|
|
87a82b16ff | ||
|
|
5666902638 | ||
|
|
f1dc232f91 | ||
|
|
5476c5a17f | ||
|
|
9aabb197e6 | ||
|
|
c2488c9269 | ||
|
|
7e40ab8e7e | ||
|
|
902b3c5033 | ||
|
|
4d1afb9fdd | ||
|
|
4209900351 | ||
|
|
780c599b49 | ||
|
|
908d35bf77 | ||
|
|
cfea84b934 | ||
|
|
e5d94e255b | ||
|
|
080858b97c | ||
|
|
d883076573 | ||
|
|
b80101fbd6 | ||
|
|
aca775f8b8 | ||
|
|
4f4a26769c | ||
|
|
d19eaa1b8e | ||
|
|
be1af0cd0f | ||
|
|
6ab02e76f1 | ||
|
|
f87bc7d72b | ||
|
|
086f4f11e1 | ||
|
|
6f54da6234 | ||
|
|
44f47f11b8 | ||
|
|
b5a2d04b2e | ||
|
|
8508ebb7d3 | ||
|
|
3234e45f33 | ||
|
|
53d1320d83 | ||
|
|
9b545809be | ||
|
|
79400acf2a | ||
|
|
3107c4103a | ||
|
|
68c88f692e | ||
|
|
df19b37d6d | ||
|
|
1b5c352566 | ||
|
|
bed6a9e4d4 | ||
|
|
d602ad1d3e | ||
|
|
51c31347df | ||
|
|
00876e7076 | ||
|
|
47eb499893 | ||
|
|
b8680c3139 | ||
|
|
9b4e3b8f74 | ||
|
|
3e858167bc | ||
|
|
44d66555cc | ||
|
|
c5438ceca3 | ||
|
|
3e1927ae44 | ||
|
|
b688117002 | ||
|
|
e62e6fb6c3 | ||
|
|
262e94e5d7 | ||
|
|
11a4b4e7ff | ||
|
|
a98cfcf28c | ||
|
|
d02f30ee62 | ||
|
|
c0bf01a646 | ||
|
|
babb985e9e | ||
|
|
e379c8317d | ||
|
|
5098277474 | ||
|
|
4a94cb6cbd | ||
|
|
e245454b91 | ||
|
|
b003eadd2d | ||
|
|
a5984d8213 | ||
|
|
4dbd95b0bb |
7
.github/labeler.yml
vendored
7
.github/labeler.yml
vendored
@@ -40,6 +40,13 @@
|
||||
- 'pcsx2-qt/**/*'
|
||||
- '3rdparty/Qt/*'
|
||||
- '3rdparty/Qt/**/*'
|
||||
'OSD / ImGui':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'pcsx2/ImGui/*'
|
||||
- 'pcsx2/ImGui/**/*'
|
||||
- '3rdparty/imgui/*'
|
||||
- '3rdparty/imgui/**/*'
|
||||
'GameDB':
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
2
.github/workflows/lint_gamedb.yml
vendored
2
.github/workflows/lint_gamedb.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install Packages
|
||||
run: |
|
||||
npm install -g ajv-cli prettier
|
||||
sudo apt-get -y install yamllint
|
||||
pip install yamllint
|
||||
|
||||
- name: Validate YAML
|
||||
run: |
|
||||
|
||||
2
.github/workflows/linux_build_flatpak.yml
vendored
2
.github/workflows/linux_build_flatpak.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
name: ${{ inputs.jobName }}
|
||||
runs-on: ${{ inputs.os }}
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.7
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.9
|
||||
options: --privileged
|
||||
timeout-minutes: 60
|
||||
|
||||
|
||||
4
.github/workflows/release_cut_new.yml
vendored
4
.github/workflows/release_cut_new.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
||||
mv ./release-notes.md ${GITHUB_WORKSPACE}/release-notes.md
|
||||
|
||||
- name: Create a GitHub Release (Manual)
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe
|
||||
if: steps.tag_version.outputs.new_tag && github.event_name == 'workflow_dispatch'
|
||||
with:
|
||||
body_path: ./release-notes.md
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
tag_name: ${{ steps.tag_version.outputs.new_tag }}
|
||||
|
||||
- name: Create a GitHub Release (Push)
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe
|
||||
if: steps.tag_version.outputs.new_tag && github.event_name != 'workflow_dispatch'
|
||||
with:
|
||||
body_path: ./release-notes.md
|
||||
|
||||
@@ -21,7 +21,7 @@ LIBJPEGTURBO=3.1.2
|
||||
LIBPNG=1.6.50
|
||||
LIBWEBP=1.6.0
|
||||
SDL=SDL3-3.2.26
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
LZ4=1.10.0
|
||||
ZSTD=1.5.7
|
||||
@@ -48,12 +48,12 @@ dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
|
||||
687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 libpng-$LIBPNG-apng.patch.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
603f2b0a259b24bd0fb14f880d7761b1d248118a42a6870cdbe8fdda4173761f qtwayland-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
49bf6db800227a6b2c971f4c5d03dd1e81297e7ffb296ce4a96437304f27cb13 qtwayland-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"app-id": "net.pcsx2.PCSX2",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.9",
|
||||
"runtime-version": "6.10",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"sdk-extensions": [
|
||||
"org.freedesktop.Sdk.Extension.llvm18"
|
||||
"org.freedesktop.Sdk.Extension.llvm20"
|
||||
],
|
||||
"add-extensions": {
|
||||
"org.freedesktop.Platform.ffmpeg-full": {
|
||||
"directory": "lib/ffmpeg",
|
||||
"version": "24.08",
|
||||
"version": "25.08",
|
||||
"add-ld-path": ".",
|
||||
"autodownload": true
|
||||
}
|
||||
@@ -50,8 +50,8 @@
|
||||
"-DCMAKE_PREFIX_PATH=\"${FLATPAK_DEST}\"",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
|
||||
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm18/bin/clang",
|
||||
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm18/bin/clang++",
|
||||
"-DCMAKE_C_COMPILER=/usr/lib/sdk/llvm20/bin/clang",
|
||||
"-DCMAKE_CXX_COMPILER=/usr/lib/sdk/llvm20/bin/clang++",
|
||||
"-DCMAKE_EXE_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
"-DCMAKE_MODULE_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
"-DCMAKE_SHARED_LINKER_FLAGS_INIT=-fuse-ld=lld",
|
||||
|
||||
@@ -48,7 +48,7 @@ LIBJPEGTURBO=3.1.2
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.0
|
||||
MOLTENVK=1.2.9
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
@@ -89,11 +89,11 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
|
||||
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
|
||||
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
|
||||
@@ -30,7 +30,7 @@ LIBJPEGTURBO=3.1.2
|
||||
LIBWEBP=1.6.0
|
||||
FFMPEG=8.0
|
||||
MOLTENVK=1.2.9
|
||||
QT=6.10.0
|
||||
QT=6.10.1
|
||||
QTAPNG=1.3.0
|
||||
KDDOCKWIDGETS=2.4.0
|
||||
PLUTOVG=1.3.1
|
||||
@@ -70,11 +70,11 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
|
||||
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
|
||||
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
|
||||
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
|
||||
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
|
||||
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
|
||||
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
|
||||
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
|
||||
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
|
||||
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
|
||||
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
|
||||
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
|
||||
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
|
||||
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
diff --git a/.cmake.conf b/.cmake.conf
|
||||
index 9a21ff42a74..d6707ba7dff 100644
|
||||
--- a/.cmake.conf
|
||||
+++ b/.cmake.conf
|
||||
@@ -51,7 +51,7 @@ set(QT_MAX_NEW_POLICY_CMAKE_VERSION_QT_APPLE "3.21")
|
||||
@@ -12,7 +11,6 @@ index 9a21ff42a74..d6707ba7dff 100644
|
||||
|
||||
set(QT_SUPPORTED_MIN_IOS_SDK_VERSION "17")
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index d3a14fc67eb..1553b956fe3 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -12,6 +12,10 @@ cmake_minimum_required(VERSION 3.16)
|
||||
@@ -27,7 +25,6 @@ index d3a14fc67eb..1553b956fe3 100644
|
||||
|
||||
qt_internal_check_if_path_has_symlinks("${CMAKE_BINARY_DIR}")
|
||||
diff --git a/src/corelib/global/qsysinfo.cpp b/src/corelib/global/qsysinfo.cpp
|
||||
index ae762c0cc6d..9171a5736b4 100644
|
||||
--- a/src/corelib/global/qsysinfo.cpp
|
||||
+++ b/src/corelib/global/qsysinfo.cpp
|
||||
@@ -1027,7 +1027,7 @@ QByteArray QSysInfo::machineUniqueId()
|
||||
@@ -40,10 +37,9 @@ index ae762c0cc6d..9171a5736b4 100644
|
||||
CFStringGetCString(stringRef, uuid, sizeof(uuid), kCFStringEncodingMacRoman);
|
||||
return QByteArray(uuid);
|
||||
diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm
|
||||
index 9f27dbe694e..c023a48cad3 100644
|
||||
--- a/src/corelib/kernel/qcore_mac.mm
|
||||
+++ b/src/corelib/kernel/qcore_mac.mm
|
||||
@@ -372,7 +372,7 @@ bool qt_apple_runningWithLiquidGlass()
|
||||
@@ -367,7 +367,7 @@ bool qt_apple_runningWithLiquidGlass()
|
||||
return config;
|
||||
#endif
|
||||
|
||||
@@ -52,8 +48,50 @@ index 9f27dbe694e..c023a48cad3 100644
|
||||
if (!nvram) {
|
||||
qWarning("Failed to locate NVRAM entry in IO registry");
|
||||
return {};
|
||||
diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm
|
||||
--- a/src/gui/platform/darwin/qappleiconengine.mm
|
||||
+++ b/src/gui/platform/darwin/qappleiconengine.mm
|
||||
@@ -366,12 +366,16 @@
|
||||
weight:NSFontWeightRegular
|
||||
scale:NSImageSymbolScaleLarge];
|
||||
|
||||
+ auto *primaryColor = [NSColor colorWithSRGBRed:color.redF()
|
||||
+ green:color.greenF()
|
||||
+ blue:color.blueF()
|
||||
+ alpha:color.alphaF()];
|
||||
+
|
||||
+ if (@available(macOS 13, *)) {
|
||||
+
|
||||
// Apply tint color first, which switches the configuration to palette mode
|
||||
config = [config configurationByApplyingConfiguration:
|
||||
- [NSImageSymbolConfiguration configurationWithPaletteColors:@[
|
||||
- [NSColor colorWithSRGBRed:color.redF() green:color.greenF()
|
||||
- blue:color.blueF() alpha:color.alphaF()]
|
||||
- ]]];
|
||||
+ [NSImageSymbolConfiguration configurationWithPaletteColors:@[primaryColor]]];
|
||||
|
||||
// Then switch back to monochrome, as palette mode gives a different look
|
||||
// than monochrome, even with a single color.
|
||||
@@ -379,6 +383,18 @@
|
||||
[NSImageSymbolConfiguration configurationPreferringMonochrome]];
|
||||
|
||||
return [image imageWithSymbolConfiguration:config];
|
||||
+
|
||||
+ } else {
|
||||
+ NSImage *configuredImage = [image imageWithSymbolConfiguration:config];
|
||||
+ return [NSImage imageWithSize:configuredImage.size flipped:NO
|
||||
+ drawingHandler:^BOOL(NSRect) {
|
||||
+ [primaryColor set];
|
||||
+ NSRect imageRect = {NSZeroPoint, configuredImage.size};
|
||||
+ [configuredImage drawInRect:imageRect];
|
||||
+ NSRectFillUsingOperation(imageRect, NSCompositingOperationSourceIn);
|
||||
+ return YES;
|
||||
+ }];
|
||||
+ }
|
||||
}
|
||||
#elif defined(QT_PLATFORM_UIKIT)
|
||||
auto *configuredImage(const UIImage *image, const QColor &color)
|
||||
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
index 6b33d94d58c..867389e4c93 100644
|
||||
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
|
||||
@@ -323,6 +323,8 @@ a normal (not maximized or full screen) top-level window.
|
||||
|
||||
@@ -48,7 +48,7 @@ set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
set LIBPNGLONG=1.6.50
|
||||
set SDL=SDL3-3.2.26
|
||||
set QT=6.10.0
|
||||
set QT=6.10.1
|
||||
set QTMINOR=6.10
|
||||
set QTAPNG=1.3.0
|
||||
set LZ4=1.10.0
|
||||
@@ -72,11 +72,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
|
||||
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
|
||||
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
|
||||
|
||||
@@ -46,7 +46,7 @@ set LIBJPEGTURBO=3.1.2
|
||||
set LIBPNG=1650
|
||||
set SDL=SDL3-3.2.26
|
||||
set LIBPNGLONG=1.6.50
|
||||
set QT=6.10.0
|
||||
set QT=6.10.1
|
||||
set QTMINOR=6.10
|
||||
set QTAPNG=1.3.0
|
||||
set LZ4=1.10.0
|
||||
@@ -70,11 +70,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
|
||||
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
|
||||
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
|
||||
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
|
||||
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
|
||||
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
|
||||
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
|
||||
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
|
||||
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
|
||||
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
|
||||
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error
|
||||
|
||||
2
3rdparty/soundtouch/CMakeLists.txt
vendored
2
3rdparty/soundtouch/CMakeLists.txt
vendored
@@ -38,7 +38,7 @@ if(NOT "${CMAKE_BUILD_TYPE}" MATCHES "Debug")
|
||||
if(MSVC)
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE /O2 /fp:fast)
|
||||
else()
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE -Ofast)
|
||||
target_compile_options(pcsx2-soundtouch PRIVATE -O3 -ffast-math)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,58 +0,0 @@
|
||||
|
||||
†---------------------†
|
||||
|
||||
Disassembly view:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Ctrl + G Goto
|
||||
Ctrl + E Edit Breakpoint
|
||||
Ctrl + D Enable/disable breakpoint
|
||||
Ctrl + B Add breakpoint
|
||||
Left Go back one branch level/goto pc
|
||||
Right Follow branch/position memory view to accessed address
|
||||
Up Move cursor up one line
|
||||
Down Move cursor down one line
|
||||
Page Up Move visible area up one page
|
||||
Page Down Move visible area down one page
|
||||
F10 Step over
|
||||
F11 Step into
|
||||
Tab Toggle display symbols
|
||||
Left Click Select line/toggle breakpoint if line is already highlighted
|
||||
Right Click Open context menu
|
||||
|
||||
†---------------------†
|
||||
|
||||
Memory View:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Ctrl + G Goto
|
||||
Ctrl + B Add breakpoint
|
||||
Left Move cursor back one byte/nibble
|
||||
Right Move cursor ahead one byte/nibble
|
||||
Up Move cursor up one line
|
||||
Down Move cursor down one line
|
||||
Page Up Move cursor up one page
|
||||
Page Down Move cursor down one page
|
||||
0-9,A-F Overwrite hex nibble
|
||||
Any Overwrite ansi byte
|
||||
Left Click Select byte/nibble
|
||||
Right Click Open context menu
|
||||
Ctrl+Wheel Zoom memory view
|
||||
Esc Return to previous goto address
|
||||
Ctrl+V Paste a hex string into memory
|
||||
|
||||
†---------------------†
|
||||
|
||||
Breakpoint List:
|
||||
|
||||
†---------------------†
|
||||
|
||||
Up Select previous item
|
||||
Down Select next item
|
||||
Delete Remove selected breakpoint
|
||||
Return Edit selected breakpoint
|
||||
Space Toggle enable state of selected breakpoint
|
||||
|
||||
†---------------------†
|
||||
File diff suppressed because it is too large
Load Diff
@@ -175,7 +175,6 @@
|
||||
030000001a1c00000001000000000000,Datel Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000451300000830000000000000,Defender Game Racer X7,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,
|
||||
03000000791d00000103000000000000,Dual Box Wii,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000c0160000e105000000000000,Dual 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:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000004f040000070f000000000000,Dual Power,a:b8,b:b9,back:b4,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,leftshoulder:b13,leftstick:b6,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b12,rightstick:b7,righttrigger:b15,start:b5,x:b10,y:b11,platform:Windows,
|
||||
030000004f04000012b3000000000000,Dual Power 3,a:b0,b:b2,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:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
030000004f04000020b3000000000000,Dual Trigger,a:b0,b:b2,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:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,
|
||||
@@ -253,7 +252,6 @@
|
||||
03000000300f00000b01000000000000,GGE909 Recoil,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:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000f0250000c283000000000000,Gioteck PlayStation Controller,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:Windows,
|
||||
03000000f025000021c1000000000000,Gioteck PS3 Controller,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,
|
||||
03000000f025000021c1000010010000,Gioteck PS3 Controller,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,
|
||||
03000000f025000031c1000000000000,Gioteck PS3 Controller,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,
|
||||
03000000f0250000c383000000000000,Gioteck VX2 PlayStation Controller,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,
|
||||
03000000f0250000c483000000000000,Gioteck VX2 PlayStation Controller,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,
|
||||
@@ -840,6 +838,7 @@
|
||||
03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,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,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000c0160000e105000000000000,XinMo Dual Arcade,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,
|
||||
xinput,XInput Controller,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:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,
|
||||
030000007d0400000340000000000000,Xterminator Digital Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:-a4,lefttrigger:+a4,leftx:a0,lefty:a1,paddle1:b7,paddle2:b6,rightshoulder:b5,rightstick:b9,righttrigger:b2,rightx:a3,righty:a5,start:b8,x:b3,y:b4,platform:Windows,
|
||||
030000002c3600000100000000000000,Yawman Arrow,+rightx:h0.2,+righty:h0.4,-rightx:h0.8,-righty:h0.1,a:b4,b:b5,back:b6,dpdown:b15,dpleft:b14,dpright:b16,dpup:b13,leftshoulder:b10,leftstick:b0,lefttrigger:-a4,leftx:a0,lefty:a1,paddle1:b11,paddle2:b12,rightshoulder:b8,rightstick:b9,righttrigger:+a4,start:b3,x:b1,y:b2,platform:Windows,
|
||||
@@ -1120,6 +1119,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005f140000c501000000020000,Trust Gamepad,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,
|
||||
03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,
|
||||
03000000632500002605000000010000,Uberwith Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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:Mac OS X,
|
||||
03000000c0160000e105000000040000,Ultimate Atari Fight Stick,a:b2,b:b4,back:b18,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,rightshoulder:b8,righttrigger:b10,start:b16,x:b0,y:b6,platform:Mac OS X,
|
||||
03000000151900005678000010010000,Uniplay U6,a:b3,b:b6,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b17,leftstick:b31,lefttrigger:b21,leftx:a1,lefty:a3,rightshoulder:b19,rightstick:b33,righttrigger:b23,rightx:a4,righty:a5,start:b27,x:b11,y:b13,platform:Mac OS X,
|
||||
030000006f0e00000302000025040000,Victrix PS4 Pro Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006f0e00000702000003060000,Victrix PS4 Pro Fightstick,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,rightshoulder:b5,righttrigger:b7,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
|
||||
@@ -1197,6 +1197,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
05000000c82d00000660000000010000,8BitDo Pro 2,a:b1,b:b0,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:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000020000000000000,8BitDo Pro 2 for Xbox,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,
|
||||
06000000c82d00000020000006010000,8BitDo Pro 2 for Xbox,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,
|
||||
03000000c82d00000960000011010000,8BitDo Pro 3,a:b1,b:b0,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:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,
|
||||
03000000c82d00000131000011010000,8BitDo Receiver,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:Linux,
|
||||
03000000c82d00000231000011010000,8BitDo Receiver,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:Linux,
|
||||
03000000c82d00000331000011010000,8BitDo Receiver,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:Linux,
|
||||
@@ -1331,6 +1332,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000006f0e00000104000000010000,Gamestop Logic3 Controller,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,
|
||||
030000008f0e00000800000010010000,Gasia PlayStation 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,
|
||||
03000000451300000010000010010000,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:Linux,
|
||||
03000000f025000021c1000010010000,Gioteck PS3 Controller,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,
|
||||
03000000f0250000c283000010010000,Gioteck VX2 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,
|
||||
190000004b4800000010000000010000,GO-Advance Controller,a:b1,b:b0,back:b10,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,leftshoulder:b4,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b13,start:b15,x:b2,y:b3,platform:Linux,
|
||||
190000004b4800000010000001010000,GO-Advance Controller,a:b1,b:b0,back:b12,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,leftshoulder:b4,leftstick:b13,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b16,righttrigger:b15,start:b17,x:b2,y:b3,platform:Linux,
|
||||
@@ -1773,6 +1775,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005f140000c501000010010000,Trust Gamepad,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:Linux,
|
||||
06000000f51000000870000003010000,Turtle Beach Recon,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,
|
||||
03000000100800000100000010010000,Twin PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000c0160000e105000010010000,Ultimate Atari Fight Stick,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,platform:Linux,
|
||||
03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,
|
||||
03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000790000000600000007010000,USB 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:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,
|
||||
|
||||
@@ -80,6 +80,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
|
||||
int DownsampleFactor = DOFFSET;
|
||||
int2 ClampMin = int2(EMODA, EMODC);
|
||||
float Weight = BGColor.x;
|
||||
float step_multiplier = BGColor.y;
|
||||
|
||||
int2 coord = max(int2(input.p.xy) * DownsampleFactor, ClampMin);
|
||||
|
||||
@@ -88,7 +89,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
output.c += Texture.Load(int3(coord + int2(xoff, yoff), 0));
|
||||
output.c += Texture.Load(int3(coord + int2(xoff * step_multiplier, yoff * step_multiplier), 0));
|
||||
}
|
||||
output.c /= Weight;
|
||||
return output;
|
||||
|
||||
@@ -70,6 +70,7 @@ void ps_depth_copy()
|
||||
uniform ivec2 ClampMin;
|
||||
uniform int DownsampleFactor;
|
||||
uniform float Weight;
|
||||
uniform float StepMultiplier;
|
||||
|
||||
void ps_downsample_copy()
|
||||
{
|
||||
@@ -78,7 +79,7 @@ void ps_downsample_copy()
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
result += texelFetch(TextureSampler, coord + ivec2(xoff, yoff), 0);
|
||||
result += texelFetch(TextureSampler, coord + ivec2(xoff * StepMultiplier, yoff * StepMultiplier), 0);
|
||||
}
|
||||
SV_Target0 = result / Weight;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ layout(push_constant) uniform cb10
|
||||
int DownsampleFactor;
|
||||
int pad0;
|
||||
float Weight;
|
||||
vec3 pad1;
|
||||
float step_multiplier;
|
||||
vec2 pad1;
|
||||
};
|
||||
void ps_downsample_copy()
|
||||
{
|
||||
@@ -75,7 +76,9 @@ void ps_downsample_copy()
|
||||
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
|
||||
{
|
||||
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
|
||||
result += texelFetch(samp0, coord + ivec2(xoff, yoff), 0);
|
||||
{
|
||||
result += texelFetch(samp0, coord + ivec2(xoff * step_multiplier, yoff * step_multiplier), 0);
|
||||
}
|
||||
}
|
||||
o_col0 = result / Weight;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ disable_compiler_warnings_for_target(speex)
|
||||
|
||||
# Find the Qt components that we need.
|
||||
if(ENABLE_QT_UI)
|
||||
find_package(Qt6 6.7.3 COMPONENTS CoreTools Core GuiTools Gui WidgetsTools Widgets LinguistTools REQUIRED)
|
||||
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)
|
||||
|
||||
@@ -99,7 +99,7 @@ __ri void Log::WriteToConsole(LOGLEVEL level, ConsoleColors color, std::string_v
|
||||
static constexpr size_t BUFFER_SIZE = 512;
|
||||
|
||||
SmallStackString<BUFFER_SIZE> buffer;
|
||||
buffer.reserve(32 + message.length());
|
||||
buffer.reserve(static_cast<u32>(32 + message.length()));
|
||||
buffer.append(s_ansi_color_codes[color]);
|
||||
|
||||
if (s_log_timestamps)
|
||||
|
||||
635
common/Image.cpp
635
common/Image.cpp
@@ -14,9 +14,6 @@
|
||||
#include <webp/decode.h>
|
||||
#include <webp/encode.h>
|
||||
|
||||
// Compute the address of a base type given a field offset.
|
||||
#define BASE_FROM_RECORD_FIELD(ptr, base_type, field) ((base_type*)(((char*)ptr) - offsetof(base_type, field)))
|
||||
|
||||
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
@@ -32,6 +29,11 @@ static bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8
|
||||
static bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
static bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||
static bool BMPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||
static bool BMPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||
static bool BMPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||
|
||||
struct FormatHandler
|
||||
{
|
||||
const char* extension;
|
||||
@@ -46,6 +48,7 @@ static constexpr FormatHandler s_format_handlers[] = {
|
||||
{"jpg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"jpeg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver},
|
||||
{"webp", WebPBufferLoader, WebPBufferSaver, WebPFileLoader, WebPFileSaver},
|
||||
{"bmp", BMPBufferLoader, BMPBufferSaver, BMPFileLoader, BMPFileSaver},
|
||||
};
|
||||
|
||||
static const FormatHandler* GetFormatHandler(const std::string_view extension)
|
||||
@@ -485,6 +488,8 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
|
||||
struct FileCallback
|
||||
{
|
||||
// Must be the first member (&this == &mgr)
|
||||
// We pass a pointer of mgr to libjpeg, and we need to be able to cast it back to FileCallback.
|
||||
jpeg_source_mgr mgr;
|
||||
|
||||
std::FILE* fp;
|
||||
@@ -496,7 +501,7 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
.mgr = {
|
||||
.init_source = [](j_decompress_ptr cinfo) {},
|
||||
.fill_input_buffer = [](j_decompress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->src);
|
||||
cb->mgr.next_input_byte = cb->buffer.get();
|
||||
if (cb->end_of_file)
|
||||
{
|
||||
@@ -513,7 +518,7 @@ bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
},
|
||||
.skip_input_data =
|
||||
[](j_decompress_ptr cinfo, long num_bytes) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->src);
|
||||
const size_t skip_in_buffer = std::min<size_t>(cb->mgr.bytes_in_buffer, static_cast<size_t>(num_bytes));
|
||||
cb->mgr.next_input_byte += skip_in_buffer;
|
||||
cb->mgr.bytes_in_buffer -= skip_in_buffer;
|
||||
@@ -650,12 +655,12 @@ bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
.mgr = {
|
||||
.init_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
cb->mgr.next_output_byte = cb->buffer.get();
|
||||
cb->mgr.free_in_buffer = BUFFER_SIZE;
|
||||
},
|
||||
.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
if (!cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp) != BUFFER_SIZE);
|
||||
|
||||
@@ -665,7 +670,7 @@ bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
},
|
||||
.term_destination =
|
||||
[](j_compress_ptr cinfo) {
|
||||
FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr);
|
||||
FileCallback* cb = reinterpret_cast<FileCallback*>(cinfo->dest);
|
||||
const size_t left = BUFFER_SIZE - cb->mgr.free_in_buffer;
|
||||
if (left > 0 && !cb->write_error)
|
||||
cb->write_error |= (std::fwrite(cb->buffer.get(), 1, left, cb->fp) != left);
|
||||
@@ -734,3 +739,617 @@ bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||
|
||||
return (std::fwrite(buffer.data(), buffer.size(), 1, fp) == 1);
|
||||
}
|
||||
|
||||
// Some of this code is adapted from Qt's BMP handler (https://github.com/qt/qtbase/blob/dev/src/gui/image/qbmphandler.cpp)
|
||||
#pragma pack(push, 1)
|
||||
struct BMPFileHeader
|
||||
{
|
||||
u16 type;
|
||||
u32 size;
|
||||
u16 reserved1;
|
||||
u16 reserved2;
|
||||
u32 offset;
|
||||
};
|
||||
|
||||
struct BMPInfoHeader
|
||||
{
|
||||
u32 size;
|
||||
s32 width;
|
||||
s32 height;
|
||||
u16 planes;
|
||||
u16 bit_count;
|
||||
u32 compression;
|
||||
u32 size_image;
|
||||
s32 x_pels_per_meter;
|
||||
s32 y_pels_per_meter;
|
||||
u32 clr_used;
|
||||
u32 clr_important;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
bool IsSupportedBMPFormat(u32 compression, u16 bit_count)
|
||||
{
|
||||
if (compression == 0)
|
||||
return (bit_count == 1 || bit_count == 4 || bit_count == 8 || bit_count == 16 || bit_count == 24 || bit_count == 32);
|
||||
|
||||
if (compression == 1)
|
||||
return (bit_count == 8);
|
||||
|
||||
if (compression == 2)
|
||||
return (bit_count == 4);
|
||||
|
||||
if (compression == 3 || compression == 4) // BMP_BITFIELDS or BMP_ALPHABITFIELDS
|
||||
return (bit_count == 16 || bit_count == 32);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LoadBMPPalette(std::vector<u32>& palette, const u8* data, u32 palette_offset, const BMPInfoHeader& info_header)
|
||||
{
|
||||
// 1 bit format doesn't use a palette in the traditional sense
|
||||
if (info_header.bit_count == 1)
|
||||
{
|
||||
palette = {0xFFFFFFFF, 0xFF000000};
|
||||
return true;
|
||||
}
|
||||
|
||||
const u32 num_colors = (info_header.clr_used > 0) ? info_header.clr_used : (1u << info_header.bit_count);
|
||||
|
||||
// Make sure that we don't have an unreasonably large palette
|
||||
if (num_colors > 256)
|
||||
{
|
||||
Console.Error("Invalid palette size: %u", num_colors);
|
||||
return false;
|
||||
}
|
||||
|
||||
palette.clear();
|
||||
palette.reserve(num_colors);
|
||||
|
||||
const u8* palette_data = data + sizeof(BMPFileHeader) + info_header.size;
|
||||
|
||||
for (u32 i = 0; i < num_colors; i++)
|
||||
{
|
||||
const u8* color = palette_data + (i * 4);
|
||||
const u8 b = color[0];
|
||||
const u8 g = color[1];
|
||||
const u8 r = color[2];
|
||||
palette.push_back(r | (g << 8) | (b << 16) | 0xFF000000u);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadUncompressedBMP(u32* pixels, const u8* src, const u8* data, u32 width, u32 height, const BMPInfoHeader& info_header, const std::vector<u32>& palette, bool flip_vertical, u32 red_mask = 0, u32 green_mask = 0, u32 blue_mask = 0, u32 alpha_mask = 0, bool use_alpha = false)
|
||||
{
|
||||
const u32 row_size = ((width * info_header.bit_count + 31) / 32) * 4;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
u32 dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
const u8* row_src = src + (y * row_size);
|
||||
u32* row_dst = pixels + (dst_y * width);
|
||||
|
||||
u32 bit_offset = 0;
|
||||
|
||||
for (u32 x = 0; x < width; x++)
|
||||
{
|
||||
u32 pixel_value = 0;
|
||||
|
||||
switch (info_header.bit_count)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u32 bit_index = 7 - (bit_offset % 8);
|
||||
pixel_value = (row_src[byte_index] >> bit_index) & 1;
|
||||
bit_offset += 1;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u32 nibble_index = (bit_offset % 8) / 4;
|
||||
pixel_value = (row_src[byte_index] >> (nibble_index * 4)) & 0xF;
|
||||
bit_offset += 4;
|
||||
break;
|
||||
}
|
||||
case 8:
|
||||
{
|
||||
pixel_value = row_src[bit_offset / 8];
|
||||
bit_offset += 8;
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
pixel_value = row_src[byte_index] | (row_src[byte_index + 1] << 8);
|
||||
bit_offset += 16;
|
||||
|
||||
if (info_header.compression == 3)
|
||||
{
|
||||
const u8* bitfields = data + sizeof(BMPFileHeader) + info_header.size;
|
||||
const u32 r_mask = *reinterpret_cast<const u32*>(bitfields);
|
||||
const u32 g_mask = *reinterpret_cast<const u32*>(bitfields + 4);
|
||||
const u32 b_mask = *reinterpret_cast<const u32*>(bitfields + 8);
|
||||
|
||||
u32 r_shift = 0, g_shift = 0, b_shift = 0;
|
||||
u32 temp = r_mask;
|
||||
while (temp >>= 1)
|
||||
r_shift++;
|
||||
temp = g_mask;
|
||||
while (temp >>= 1)
|
||||
g_shift++;
|
||||
temp = b_mask;
|
||||
while (temp >>= 1)
|
||||
b_shift++;
|
||||
|
||||
const u8 r = static_cast<u8>((pixel_value & r_mask) >> r_shift);
|
||||
const u8 g = static_cast<u8>((pixel_value & g_mask) >> g_shift);
|
||||
const u8 b = static_cast<u8>((pixel_value & b_mask) >> b_shift);
|
||||
|
||||
const u8 r_max = static_cast<u8>(r_mask >> r_shift);
|
||||
const u8 g_max = static_cast<u8>(g_mask >> g_shift);
|
||||
const u8 b_max = static_cast<u8>(b_mask >> b_shift);
|
||||
|
||||
const u8 r_scaled = (r_max > 0) ? static_cast<u8>((r * 255) / r_max) : 0;
|
||||
const u8 g_scaled = (g_max > 0) ? static_cast<u8>((g * 255) / g_max) : 0;
|
||||
const u8 b_scaled = (b_max > 0) ? static_cast<u8>((b * 255) / b_max) : 0;
|
||||
|
||||
row_dst[x] = r_scaled | (g_scaled << 8) | (b_scaled << 16) | 0xFF000000u;
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 r = (pixel_value >> 10) & 0x1F;
|
||||
const u8 g = (pixel_value >> 5) & 0x1F;
|
||||
const u8 b = pixel_value & 0x1F;
|
||||
row_dst[x] = (r << 3) | (g << 11) | (b << 19) | 0xFF000000u;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
case 24:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
const u8 b = row_src[byte_index + 0];
|
||||
const u8 g = row_src[byte_index + 1];
|
||||
const u8 r = row_src[byte_index + 2];
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | 0xFF000000u;
|
||||
bit_offset += 24;
|
||||
continue;
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
const u32 byte_index = bit_offset / 8;
|
||||
u32 pixel_value = row_src[byte_index] | (row_src[byte_index + 1] << 8) | (row_src[byte_index + 2] << 16) | (row_src[byte_index + 3] << 24);
|
||||
bit_offset += 32;
|
||||
|
||||
if (info_header.compression == 3 || info_header.compression == 4) // BITFIELDS or ALPHABITFIELDS
|
||||
{
|
||||
// Calculate shifts
|
||||
auto calc_shift = [](u32 mask) -> u32 {
|
||||
u32 result = 0;
|
||||
while ((mask >= 0x100) || (!(mask & 1) && mask))
|
||||
{
|
||||
result++;
|
||||
mask >>= 1;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Calculate scales
|
||||
auto calc_scale = [](u32 low_mask) -> u32 {
|
||||
u32 result = 8;
|
||||
while (low_mask && result)
|
||||
{
|
||||
result--;
|
||||
low_mask >>= 1;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Apply scale
|
||||
auto apply_scale = [](u32 value, u32 scale) -> u8 {
|
||||
if (!(scale & 0x07)) // scale == 8 or 0
|
||||
return static_cast<u8>(value);
|
||||
u32 filled = 8 - scale;
|
||||
u32 result = value << scale;
|
||||
do
|
||||
{
|
||||
result |= result >> filled;
|
||||
filled <<= 1;
|
||||
} while (filled < 8);
|
||||
return static_cast<u8>(result);
|
||||
};
|
||||
|
||||
const u32 r_shift = calc_shift(red_mask);
|
||||
const u32 g_shift = calc_shift(green_mask);
|
||||
const u32 b_shift = calc_shift(blue_mask);
|
||||
const u32 a_shift = (alpha_mask != 0) ? calc_shift(alpha_mask) : 0;
|
||||
|
||||
const u32 r_scale = calc_scale(red_mask >> r_shift);
|
||||
const u32 g_scale = calc_scale(green_mask >> g_shift);
|
||||
const u32 b_scale = calc_scale(blue_mask >> b_shift);
|
||||
const u32 a_scale = (alpha_mask != 0) ? calc_scale(alpha_mask >> a_shift) : 0;
|
||||
|
||||
const u8 r = apply_scale((pixel_value & red_mask) >> r_shift, r_scale);
|
||||
const u8 g = apply_scale((pixel_value & green_mask) >> g_shift, g_scale);
|
||||
const u8 b = apply_scale((pixel_value & blue_mask) >> b_shift, b_scale);
|
||||
const u8 a = (use_alpha && alpha_mask != 0) ? apply_scale((pixel_value & alpha_mask) >> a_shift, a_scale) : 0xFF;
|
||||
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Uncompressed 32-bit BGRA order
|
||||
const u8 b = row_src[byte_index + 0];
|
||||
const u8 g = row_src[byte_index + 1];
|
||||
const u8 r = row_src[byte_index + 2];
|
||||
const u8 a = row_src[byte_index + 3];
|
||||
row_dst[x] = r | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (info_header.bit_count <= 8)
|
||||
{
|
||||
if (pixel_value < palette.size())
|
||||
row_dst[x] = palette[pixel_value];
|
||||
else
|
||||
{
|
||||
Console.Error("Invalid palette index: %u (palette size: %zu)", pixel_value, palette.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadCompressedBMP(u32* pixels, const u8* src, u32 src_size, u32 width, u32 height, const BMPInfoHeader& info_header, const std::vector<u32>& palette, bool flip_vertical)
|
||||
{
|
||||
u32 src_pos = 0;
|
||||
const u32 pixel_size = (info_header.bit_count == 8) ? 1 : 2;
|
||||
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
u32 dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
u32* row_dst = pixels + (dst_y * width);
|
||||
u32 x = 0;
|
||||
|
||||
while (x < width)
|
||||
{
|
||||
// Check bounds before reading
|
||||
if (src_pos + 2 > src_size)
|
||||
return false;
|
||||
|
||||
const u8 count = src[src_pos++];
|
||||
const u8 value = src[src_pos++];
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
if (value == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (value == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (value == 2)
|
||||
{
|
||||
// Delta (jump) need 2 more bytes
|
||||
if (src_pos + 2 > src_size)
|
||||
return false;
|
||||
const u8 dx = src[src_pos++];
|
||||
const u8 dy = src[src_pos++];
|
||||
x += dx;
|
||||
y += dy;
|
||||
if (y >= height || x >= width)
|
||||
return false;
|
||||
const u32 new_dst_y = flip_vertical ? (height - 1 - y) : y;
|
||||
row_dst = pixels + (new_dst_y * width);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Absolute mode need "value" bytes of pixel data
|
||||
const u32 run_length = value;
|
||||
const u32 bytes_needed = run_length * pixel_size;
|
||||
if (src_pos + bytes_needed > src_size)
|
||||
return false;
|
||||
|
||||
for (u32 i = 0; i < run_length; i++)
|
||||
{
|
||||
if (x >= width)
|
||||
break;
|
||||
|
||||
u8 pixel_value = 0;
|
||||
if (info_header.bit_count == 8)
|
||||
{
|
||||
pixel_value = src[src_pos++];
|
||||
}
|
||||
else
|
||||
{
|
||||
const u8 byte_val = src[src_pos++];
|
||||
pixel_value = (i % 2 == 0) ? (byte_val >> 4) : (byte_val & 0x0F);
|
||||
}
|
||||
|
||||
row_dst[x++] = (pixel_value < palette.size()) ? palette[pixel_value] : 0;
|
||||
}
|
||||
|
||||
if ((run_length * pixel_size) % 2 == 1)
|
||||
src_pos++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 pixel_value = value;
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
if (x >= width)
|
||||
break;
|
||||
row_dst[x++] = (pixel_value < palette.size()) ? palette[pixel_value] : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||
{
|
||||
if (buffer_size < sizeof(BMPFileHeader) + sizeof(BMPInfoHeader))
|
||||
{
|
||||
Console.Error("BMP file too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u8* data = static_cast<const u8*>(buffer);
|
||||
BMPFileHeader file_header;
|
||||
BMPInfoHeader info_header;
|
||||
|
||||
std::memcpy(&file_header, data, sizeof(BMPFileHeader));
|
||||
std::memcpy(&info_header, data + sizeof(BMPFileHeader), sizeof(BMPInfoHeader));
|
||||
|
||||
if (file_header.type != 0x4D42)
|
||||
{
|
||||
Console.Error("Invalid BMP signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for extended header versions (V4=108 bytes, V5=124 bytes)
|
||||
// We read as BITMAPINFOHEADER (40 bytes) regardless, since extended headers just add fields at the end
|
||||
if (info_header.size == 108)
|
||||
{
|
||||
Console.Warning("BITMAPV4HEADER detected, reading as BITMAPINFOHEADER");
|
||||
}
|
||||
else if (info_header.size == 124)
|
||||
{
|
||||
Console.Warning("BITMAPV5HEADER detected, reading as BITMAPINFOHEADER");
|
||||
}
|
||||
else if (info_header.size != 40)
|
||||
{
|
||||
Console.Warning("Unknown BMP header size: %u, attempting to read as BITMAPINFOHEADER", info_header.size);
|
||||
}
|
||||
|
||||
if (!IsSupportedBMPFormat(info_header.compression, info_header.bit_count))
|
||||
{
|
||||
Console.Error("Unsupported BMP format: compression=%u, bit_count=%u", info_header.compression, info_header.bit_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 width = static_cast<u32>(std::abs(info_header.width));
|
||||
const u32 height = static_cast<u32>(std::abs(info_header.height));
|
||||
const bool flip_vertical = (info_header.height > 0);
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
Console.Error("Invalid BMP dimensions: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (width > 65536 || height > 65536)
|
||||
{
|
||||
Console.Error("BMP dimensions too large: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLn("BMP: %ux%u, %u-bit, compression=%u", width, height, info_header.bit_count, info_header.compression);
|
||||
|
||||
// Read color masks from header or bitfields
|
||||
u32 red_mask = 0;
|
||||
u32 green_mask = 0;
|
||||
u32 blue_mask = 0;
|
||||
u32 alpha_mask = 0;
|
||||
const bool bitfields = (info_header.compression == 3 || info_header.compression == 4); // BMP_BITFIELDS or BMP_ALPHABITFIELDS
|
||||
const u8* header_start = data + sizeof(BMPFileHeader);
|
||||
const u32 header_base_offset = sizeof(BMPFileHeader) + 40; // Base header is 40 bytes
|
||||
|
||||
if (info_header.size >= 108) // BMP_WIN4 (108) or BMP_WIN5 (124)
|
||||
{
|
||||
// V4/V5 headers masks come right after the 40-byte base header
|
||||
// Masks are at offsets from header_start: red=40, green=44, blue=48, alpha=52
|
||||
if (buffer_size >= header_base_offset + 16) // Need space for 4 masks
|
||||
{
|
||||
red_mask = *reinterpret_cast<const u32*>(header_start + 40);
|
||||
green_mask = *reinterpret_cast<const u32*>(header_start + 44);
|
||||
blue_mask = *reinterpret_cast<const u32*>(header_start + 48);
|
||||
alpha_mask = *reinterpret_cast<const u32*>(header_start + 52);
|
||||
}
|
||||
}
|
||||
else if (bitfields && (info_header.bit_count == 16 || info_header.bit_count == 32))
|
||||
{
|
||||
const u32 bitfields_offset = sizeof(BMPFileHeader) + info_header.size;
|
||||
if (buffer_size >= bitfields_offset + 12) // Need space for at least r/g/b masks
|
||||
{
|
||||
red_mask = *reinterpret_cast<const u32*>(data + bitfields_offset);
|
||||
green_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 4);
|
||||
blue_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 8);
|
||||
if (info_header.compression == 4) // BMP_ALPHABITFIELDS
|
||||
{
|
||||
// Read alpha mask: r, g, b, a
|
||||
if (buffer_size >= bitfields_offset + 16)
|
||||
alpha_mask = *reinterpret_cast<const u32*>(data + bitfields_offset + 12);
|
||||
}
|
||||
// For BMP_BITFIELDS (3), alpha_mask stays 0
|
||||
}
|
||||
}
|
||||
|
||||
bool use_alpha = bitfields || (info_header.compression == 0 && info_header.bit_count == 32 && alpha_mask == 0xff000000);
|
||||
use_alpha = use_alpha && (alpha_mask != 0);
|
||||
|
||||
const u32 bytes_per_pixel = info_header.bit_count / 8;
|
||||
const u32 row_size = ((width * bytes_per_pixel + 3) / 4) * 4;
|
||||
|
||||
// For uncompressed BMPs, verify we have enough data
|
||||
// For RLE-compressed BMPs, size is variable so we check differently
|
||||
if (info_header.compression == 0)
|
||||
{
|
||||
if (file_header.offset + (row_size * height) > buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// For RLE-compressed BMPs, check that we have at least the offset and some data
|
||||
// Use biSizeImage if available, otherwise just verify offset is valid
|
||||
if (file_header.offset >= buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
if (info_header.size_image > 0)
|
||||
{
|
||||
if (file_header.offset + info_header.size_image > buffer_size)
|
||||
{
|
||||
Console.Error("BMP file data incomplete");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u32> pixels;
|
||||
pixels.resize(width * height);
|
||||
|
||||
const u8* src = data + file_header.offset;
|
||||
const u32 src_size = buffer_size - file_header.offset;
|
||||
|
||||
std::vector<u32> palette;
|
||||
if (info_header.bit_count <= 8)
|
||||
{
|
||||
if (!LoadBMPPalette(palette, data, file_header.offset, info_header))
|
||||
{
|
||||
Console.Error("Failed to load BMP palette");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (info_header.compression == 0 || info_header.compression == 3 || info_header.compression == 4)
|
||||
{
|
||||
if (!LoadUncompressedBMP(pixels.data(), src, data, width, height, info_header, palette, flip_vertical, red_mask, green_mask, blue_mask, alpha_mask, use_alpha))
|
||||
{
|
||||
Console.Error("Failed to load uncompressed BMP data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!LoadCompressedBMP(pixels.data(), src, src_size, width, height, info_header, palette, flip_vertical))
|
||||
{
|
||||
Console.Error("Failed to load compressed BMP data");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle alpha channel for 32-bit BMPs
|
||||
// Only use alpha if alpha_mask is explicitly set in header/bitfields
|
||||
if (info_header.bit_count == 32 && !use_alpha)
|
||||
{
|
||||
// Alpha mask not set or zero - set all pixels to fully opaque
|
||||
for (u32& pixel : pixels)
|
||||
pixel |= 0xFF000000u;
|
||||
}
|
||||
|
||||
image->SetPixels(width, height, std::move(pixels));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||
{
|
||||
std::optional<std::vector<u8>> data = FileSystem::ReadBinaryFile(fp);
|
||||
if (!data.has_value())
|
||||
return false;
|
||||
|
||||
return BMPBufferLoader(image, data->data(), data->size());
|
||||
}
|
||||
|
||||
bool BMPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||
{
|
||||
const u32 width = image.GetWidth();
|
||||
const u32 height = image.GetHeight();
|
||||
|
||||
// Check dimensions
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
Console.Error("Invalid BMP dimensions: %ux%u", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
const u32 row_size = ((width * 3 + 3) / 4) * 4;
|
||||
const u32 image_size = row_size * height;
|
||||
const u32 file_size = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + image_size;
|
||||
|
||||
buffer->resize(file_size);
|
||||
u8* data = buffer->data();
|
||||
|
||||
BMPFileHeader file_header = {};
|
||||
file_header.type = 0x4D42;
|
||||
file_header.size = file_size;
|
||||
file_header.reserved1 = 0;
|
||||
file_header.reserved2 = 0;
|
||||
file_header.offset = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
|
||||
std::memcpy(data, &file_header, sizeof(BMPFileHeader));
|
||||
|
||||
BMPInfoHeader info_header = {};
|
||||
info_header.size = sizeof(BMPInfoHeader);
|
||||
info_header.width = static_cast<s32>(width);
|
||||
info_header.height = static_cast<s32>(height);
|
||||
info_header.planes = 1;
|
||||
info_header.bit_count = 24;
|
||||
info_header.compression = 0;
|
||||
info_header.size_image = image_size;
|
||||
info_header.x_pels_per_meter = 0;
|
||||
info_header.y_pels_per_meter = 0;
|
||||
info_header.clr_used = 0;
|
||||
info_header.clr_important = 0;
|
||||
std::memcpy(data + sizeof(BMPFileHeader), &info_header, sizeof(BMPInfoHeader));
|
||||
|
||||
u8* pixel_data = data + file_header.offset;
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
const u32 src_y = height - 1 - y;
|
||||
const u32* row_src = image.GetRowPixels(src_y);
|
||||
u8* row_dst = pixel_data + (y * row_size);
|
||||
|
||||
for (u32 x = 0; x < width; x++)
|
||||
{
|
||||
const u32 rgba = row_src[x];
|
||||
row_dst[x * 3 + 0] = static_cast<u8>((rgba >> 16) & 0xFF);
|
||||
row_dst[x * 3 + 1] = static_cast<u8>((rgba >> 8) & 0xFF);
|
||||
row_dst[x * 3 + 2] = static_cast<u8>(rgba & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||
{
|
||||
std::vector<u8> buffer;
|
||||
if (!BMPBufferSaver(image, &buffer, quality))
|
||||
return false;
|
||||
|
||||
return (std::fwrite(buffer.data(), buffer.size(), 1, fp) == 1);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,11 @@ namespace Common
|
||||
Reset();
|
||||
}
|
||||
|
||||
Timer::Timer(Value start_value)
|
||||
{
|
||||
m_tvStartValue = start_value;
|
||||
}
|
||||
|
||||
void Timer::Reset()
|
||||
{
|
||||
m_tvStartValue = GetCurrentValue();
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Common
|
||||
using Value = std::uint64_t;
|
||||
|
||||
Timer();
|
||||
Timer (Value start_value);
|
||||
|
||||
static Value GetCurrentValue();
|
||||
static double ConvertValueToSeconds(Value value);
|
||||
|
||||
@@ -446,8 +446,8 @@ static void PrintCommandLineHelp(const char* progname)
|
||||
std::fprintf(stderr, " -help: Displays this information and exits.\n");
|
||||
std::fprintf(stderr, " -version: Displays version information and exits.\n");
|
||||
std::fprintf(stderr, " -dumpdir <dir>: Frame dump directory (will be dumped as filename_frameN.png).\n");
|
||||
std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr]: Enabling dumping of render target, texture, z buffer, frame, "
|
||||
"alphas, and info (context, vertices, transfers (list)), transfers (images), respectively, per draw. Generates lots of data.\n");
|
||||
std::fprintf(stderr, " -dump [rt|tex|z|f|a|i|tr|ds|fs]: Enabling dumping of render target, texture, z buffer, frame, "
|
||||
"alphas, and info (context, vertices, list of transfers), transfers images, draw stats, frame stats, respectively, per draw. Generates lots of data.\n");
|
||||
std::fprintf(stderr, " -dumprange N[,L,B]: Start dumping from draw N (base 0), stops after L draws, and only "
|
||||
"those draws that are multiples of B (intersection of -dumprange and -dumprangef used)."
|
||||
"Defaults to 0,-1,1 (all draws). Only used if -dump used.\n");
|
||||
@@ -533,6 +533,10 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveInfo", true);
|
||||
if (str.find("tr") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveTransferImages", true);
|
||||
if (str.find("ds") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveDrawStats", true);
|
||||
if (str.find("fs") != std::string::npos)
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "SaveFrameStats", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumprange"))
|
||||
@@ -726,7 +730,7 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
else if (CHECK_ARG("-noshadercache"))
|
||||
{
|
||||
Console.WriteLn("Disabling shader cache");
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "disable_shader_cache", true);
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "DisableShaderCache", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("-window"))
|
||||
@@ -857,16 +861,27 @@ void GSRunner::DumpStats()
|
||||
#define main real_main
|
||||
#endif
|
||||
|
||||
static void CPUThreadMain(VMBootParameters* params) {
|
||||
if (VMManager::Initialize(*params))
|
||||
static void CPUThreadMain(VMBootParameters* params, std::atomic<int>* ret)
|
||||
{
|
||||
ret->store(EXIT_FAILURE);
|
||||
|
||||
if (VMManager::Internal::CPUThreadInitialize())
|
||||
{
|
||||
// run until end
|
||||
GSDumpReplayer::SetLoopCount(s_loop_count);
|
||||
VMManager::SetState(VMState::Running);
|
||||
while (VMManager::GetState() == VMState::Running)
|
||||
VMManager::Execute();
|
||||
VMManager::Shutdown(false);
|
||||
GSRunner::DumpStats();
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
|
||||
if (VMManager::Initialize(*params))
|
||||
{
|
||||
// run until end
|
||||
GSDumpReplayer::SetLoopCount(s_loop_count);
|
||||
VMManager::SetState(VMState::Running);
|
||||
while (VMManager::GetState() == VMState::Running)
|
||||
VMManager::Execute();
|
||||
VMManager::Shutdown(false);
|
||||
GSRunner::DumpStats();
|
||||
ret->store(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
VMManager::Internal::CPUThreadShutdown();
|
||||
@@ -888,9 +903,6 @@ int main(int argc, char* argv[])
|
||||
if (!GSRunner::ParseCommandLineArgs(argc, argv, params))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if (!VMManager::Internal::CPUThreadInitialize())
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if (s_use_window.value_or(true) && !GSRunner::CreatePlatformWindow())
|
||||
{
|
||||
Console.Error("Failed to create window.");
|
||||
@@ -900,18 +912,14 @@ int main(int argc, char* argv[])
|
||||
// Override settings that shouldn't be picked up from defaults or INIs.
|
||||
GSRunner::SettingsOverride();
|
||||
|
||||
// apply new settings (e.g. pick up renderer change)
|
||||
VMManager::ApplySettings();
|
||||
GSDumpReplayer::SetIsDumpRunner(true);
|
||||
|
||||
std::thread cputhread(CPUThreadMain, ¶ms);
|
||||
std::atomic<int> thread_ret;
|
||||
std::thread cputhread(CPUThreadMain, ¶ms, &thread_ret);
|
||||
GSRunner::PumpPlatformMessages(/*forever=*/true);
|
||||
cputhread.join();
|
||||
|
||||
VMManager::Internal::CPUThreadShutdown();
|
||||
GSRunner::DestroyPlatformWindow();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
return thread_ret.load();
|
||||
}
|
||||
|
||||
void Host::PumpMessagesOnCPUThread()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/SmallString.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QString>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtWidgets/QDialog>
|
||||
@@ -134,12 +135,16 @@ void AboutDialog::showHTMLDialog(QWidget* parent, const QString& title, const QS
|
||||
tb->setOpenExternalLinks(true);
|
||||
|
||||
QFile file(path);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
if (const QByteArray data = file.readAll(); !data.isEmpty())
|
||||
tb->setText(QString::fromUtf8(data));
|
||||
else
|
||||
QFileInfo fi(path);
|
||||
if (!fi.exists() || !fi.isReadable())
|
||||
{
|
||||
tb->setText(tr("File not found: %1").arg(path));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
tb->setSource(QUrl::fromLocalFile(path));
|
||||
}
|
||||
|
||||
layout->addWidget(tb, 1);
|
||||
|
||||
QDialogButtonBox* bb = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
|
||||
|
||||
@@ -153,7 +153,7 @@ void DebuggerSettingsManager::saveGameSettings(QAbstractTableModel* abstractTabl
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
const std::lock_guard<std::mutex> lock(writeLock);
|
||||
std::lock_guard<std::mutex> lock(writeLock);
|
||||
QJsonObject loadedSettings = loadGameSettingsJSON();
|
||||
QJsonArray rowsArray;
|
||||
QStringList keys;
|
||||
|
||||
@@ -118,11 +118,11 @@ void DisassemblyView::contextPasteInstructionText()
|
||||
// split text in clipboard by new lines
|
||||
QString clipboardText = QApplication::clipboard()->text();
|
||||
std::vector<std::string> newInstructions = StringUtil::splitOnNewLine(clipboardText.toLocal8Bit().constData());
|
||||
int newInstructionsSize = newInstructions.size();
|
||||
u32 newInstructionsSize = static_cast<u32>(newInstructions.size());
|
||||
|
||||
// validate new instructions before pasting them
|
||||
std::vector<u32> encodedInstructions;
|
||||
for (int instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
for (u32 instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
{
|
||||
u32 replaceAddress = m_selectedAddressStart + instructionIdx * 4;
|
||||
u32 encodedInstruction;
|
||||
@@ -137,7 +137,7 @@ void DisassemblyView::contextPasteInstructionText()
|
||||
}
|
||||
|
||||
// paste validated instructions
|
||||
for (int instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
for (u32 instructionIdx = 0; instructionIdx < newInstructionsSize; instructionIdx++)
|
||||
{
|
||||
u32 replaceAddress = m_selectedAddressStart + instructionIdx * 4;
|
||||
setInstructions(replaceAddress, replaceAddress, encodedInstructions[instructionIdx]);
|
||||
@@ -424,7 +424,7 @@ void DisassemblyView::paintEvent(QPaintEvent* event)
|
||||
bool alternate = m_visibleStart % 8;
|
||||
|
||||
// Draw visible disassembly rows
|
||||
for (u32 i = 0; i <= m_visibleRows; i++)
|
||||
for (u32 i = 0; i < m_visibleRows + 1; i++)
|
||||
{
|
||||
// Address of instruction being displayed on row
|
||||
const u32 rowAddress = (i * 4) + m_visibleStart;
|
||||
@@ -977,18 +977,18 @@ QColor DisassemblyView::GetAddressFunctionColor(u32 address)
|
||||
QString DisassemblyView::FetchSelectionInfo(SelectionInfo selInfo)
|
||||
{
|
||||
QString infoBlock;
|
||||
for (u32 i = m_selectedAddressStart; i <= m_selectedAddressEnd; i += 4)
|
||||
for (u64 i = m_selectedAddressStart; i <= m_selectedAddressEnd; i += 4)
|
||||
{
|
||||
if (i != m_selectedAddressStart)
|
||||
infoBlock += '\n';
|
||||
if (selInfo == SelectionInfo::ADDRESS)
|
||||
{
|
||||
infoBlock += FilledQStringFromValue(i, 16);
|
||||
infoBlock += FilledQStringFromValue(static_cast<u32>(i), 16);
|
||||
}
|
||||
else if (selInfo == SelectionInfo::INSTRUCTIONTEXT)
|
||||
{
|
||||
DisassemblyLineInfo line;
|
||||
m_disassemblyManager.getLine(i, true, line);
|
||||
m_disassemblyManager.getLine(static_cast<u32>(i), true, line);
|
||||
infoBlock += QString("%1 %2").arg(line.name.c_str()).arg(line.params.c_str());
|
||||
}
|
||||
else // INSTRUCTIONHEX
|
||||
@@ -1075,9 +1075,9 @@ void DisassemblyView::setInstructions(u32 start, u32 end, u32 value)
|
||||
|
||||
bool DisassemblyView::AddressCanRestore(u32 start, u32 end)
|
||||
{
|
||||
for (u32 i = start; i <= end; i += 4)
|
||||
for (u64 i = start; i <= end; i += 4)
|
||||
{
|
||||
if (this->m_nopedInstructions.find(i) != this->m_nopedInstructions.end())
|
||||
if (this->m_nopedInstructions.find(static_cast<u32>(i)) != this->m_nopedInstructions.end())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,39 +6,36 @@
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtGui/QPaintEvent>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QStyleFactory>
|
||||
#include <QtWidgets/QStyleOption>
|
||||
|
||||
static const int OUTER_MENU_MARGIN = 2;
|
||||
static const int INNER_MENU_MARGIN = 4;
|
||||
static constexpr int TAB_BAR_TOP_MARGIN = 2;
|
||||
static constexpr int RIGHT_MARGIN = 2;
|
||||
|
||||
DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_original_menu_bar(original_menu_bar)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, OUTER_MENU_MARGIN, OUTER_MENU_MARGIN, 0);
|
||||
setLayout(layout);
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, RIGHT_MARGIN, 0);
|
||||
|
||||
QWidget* menu_wrapper = new QWidget;
|
||||
menu_wrapper->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
layout->addWidget(menu_wrapper);
|
||||
QWidget* menu_bar_wrapper = new QWidget;
|
||||
layout->addWidget(menu_bar_wrapper);
|
||||
|
||||
QHBoxLayout* menu_layout = new QHBoxLayout;
|
||||
menu_layout->setContentsMargins(0, INNER_MENU_MARGIN, 0, INNER_MENU_MARGIN);
|
||||
menu_wrapper->setLayout(menu_layout);
|
||||
QVBoxLayout* menu_bar_layout = new QVBoxLayout(menu_bar_wrapper);
|
||||
menu_bar_layout->setContentsMargins(0, 0, 0, 0);
|
||||
menu_bar_layout->addWidget(original_menu_bar, 0, Qt::AlignVCenter);
|
||||
|
||||
menu_layout->addWidget(original_menu_bar);
|
||||
QWidget* layout_switcher_wrapper = new QWidget;
|
||||
layout->addWidget(layout_switcher_wrapper);
|
||||
|
||||
m_layout_switcher_layout = new QVBoxLayout(layout_switcher_wrapper);
|
||||
|
||||
m_layout_switcher = new QTabBar;
|
||||
m_layout_switcher->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout_switcher->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_layout_switcher->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_layout_switcher->setDrawBase(false);
|
||||
m_layout_switcher->setExpanding(false);
|
||||
m_layout_switcher->setMovable(true);
|
||||
layout->addWidget(m_layout_switcher);
|
||||
|
||||
connect(m_layout_switcher, &QTabBar::tabMoved, this, [this](int from, int to) {
|
||||
DockLayout::Index from_index = static_cast<DockLayout::Index>(from);
|
||||
@@ -63,6 +60,10 @@ DockMenuBar::DockMenuBar(QWidget* original_menu_bar, QWidget* parent)
|
||||
});
|
||||
layout->addWidget(m_layout_locked_toggle);
|
||||
|
||||
layout->setStretchFactor(menu_bar_wrapper, 0);
|
||||
layout->setStretchFactor(layout_switcher_wrapper, 1);
|
||||
layout->setStretchFactor(m_layout_locked_toggle, 0);
|
||||
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
@@ -75,6 +76,19 @@ void DockMenuBar::updateTheme()
|
||||
|
||||
delete m_style;
|
||||
m_style = style;
|
||||
|
||||
// Vertically centre the layout switcher tabs for the Windows 11 style
|
||||
// because I think it looks better. Do the same for macOS too.
|
||||
if (style->baseStyle()->name() == "windows11" || style->baseStyle()->name() == "macOS")
|
||||
{
|
||||
m_layout_switcher_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout_switcher_layout->addWidget(m_layout_switcher, 0, Qt::AlignVCenter);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_layout_switcher_layout->setContentsMargins(0, TAB_BAR_TOP_MARGIN, 0, 0);
|
||||
m_layout_switcher_layout->addWidget(m_layout_switcher, 0, Qt::AlignBottom);
|
||||
}
|
||||
}
|
||||
|
||||
void DockMenuBar::updateLayoutSwitcher(DockLayout::Index current_index, const std::vector<DockLayout>& layouts)
|
||||
@@ -182,11 +196,6 @@ void DockMenuBar::stopBlink()
|
||||
}
|
||||
}
|
||||
|
||||
int DockMenuBar::innerHeight() const
|
||||
{
|
||||
return m_original_menu_bar->sizeHint().height() + INNER_MENU_MARGIN * 2;
|
||||
}
|
||||
|
||||
void DockMenuBar::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
QPainter painter(this);
|
||||
@@ -236,39 +245,6 @@ void DockMenuBarStyle::drawControl(
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case CE_MenuBarItem:
|
||||
{
|
||||
const QStyleOptionMenuItem* opt = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
|
||||
if (!opt)
|
||||
break;
|
||||
|
||||
QWidget* menu_wrapper = widget->parentWidget();
|
||||
if (!menu_wrapper)
|
||||
break;
|
||||
|
||||
const DockMenuBar* menu_bar = qobject_cast<const DockMenuBar*>(menu_wrapper->parentWidget());
|
||||
if (!menu_bar)
|
||||
break;
|
||||
|
||||
if (baseStyle()->name() != "fusion")
|
||||
break;
|
||||
|
||||
// This mirrors a check in QFusionStyle::drawControl. If act is
|
||||
// false, QFusionStyle will try to draw a border along the bottom.
|
||||
bool act = opt->state & State_Selected && opt->state & State_Sunken;
|
||||
if (act)
|
||||
break;
|
||||
|
||||
// Extend the menu item to the bottom of the menu bar to fix the
|
||||
// position in which it draws its bottom border. We also need to
|
||||
// extend it up by the same amount so that the text isn't moved.
|
||||
QStyleOptionMenuItem menu_opt = *opt;
|
||||
int difference = (menu_bar->innerHeight() - option->rect.top()) - menu_opt.rect.height();
|
||||
menu_opt.rect.adjust(0, -difference, 0, difference);
|
||||
QProxyStyle::drawControl(element, &menu_opt, painter, widget);
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_TabBarTab:
|
||||
{
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
@@ -286,6 +262,25 @@ void DockMenuBarStyle::drawControl(
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_MenuBarItem:
|
||||
{
|
||||
const QStyleOptionMenuItem* opt = qstyleoption_cast<const QStyleOptionMenuItem*>(option);
|
||||
if (!opt)
|
||||
break;
|
||||
|
||||
if (baseStyle()->name() != "fusion")
|
||||
break;
|
||||
|
||||
// This mirrors a check in QFusionStyle::drawControl. If act is
|
||||
// false, QFusionStyle will try to draw a border along the bottom.
|
||||
bool act = opt->state & State_Selected && opt->state & State_Sunken;
|
||||
if (act)
|
||||
break;
|
||||
|
||||
QCommonStyle::drawControl(element, option, painter, widget);
|
||||
|
||||
return;
|
||||
}
|
||||
case CE_MenuBarEmptyArea:
|
||||
{
|
||||
// Prevent it from drawing a border in the wrong position.
|
||||
@@ -301,11 +296,13 @@ void DockMenuBarStyle::drawControl(
|
||||
}
|
||||
|
||||
QSize DockMenuBarStyle::sizeFromContents(
|
||||
QStyle::ContentsType type, const QStyleOption* option, const QSize& contents_size, const QWidget* widget) const
|
||||
QStyle::ContentsType type,
|
||||
const QStyleOption* option,
|
||||
const QSize& contents_size,
|
||||
const QWidget* widget) const
|
||||
{
|
||||
QSize size = QProxyStyle::sizeFromContents(type, option, contents_size, widget);
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
// Adjust the sizes of the layout switcher tabs depending on the theme.
|
||||
if (type == CT_TabBarTab)
|
||||
{
|
||||
@@ -313,30 +310,12 @@ QSize DockMenuBarStyle::sizeFromContents(
|
||||
if (!opt)
|
||||
return size;
|
||||
|
||||
const QTabBar* tab_bar = qobject_cast<const QTabBar*>(widget);
|
||||
if (!tab_bar)
|
||||
return size;
|
||||
|
||||
const DockMenuBar* menu_bar = qobject_cast<const DockMenuBar*>(tab_bar->parentWidget());
|
||||
if (!menu_bar)
|
||||
return size;
|
||||
|
||||
if (baseStyle()->name() == "fusion" || baseStyle()->name() == "windowsvista")
|
||||
if (baseStyle()->name() == "windows11")
|
||||
{
|
||||
// Make sure the tab extends to the bottom of the widget.
|
||||
size.setHeight(menu_bar->innerHeight() - opt->rect.top());
|
||||
}
|
||||
else if (baseStyle()->name() == "windows11")
|
||||
{
|
||||
// Adjust the size of the tab such that it is vertically centred.
|
||||
size.setHeight(menu_bar->innerHeight() - opt->rect.top() * 2 - OUTER_MENU_MARGIN);
|
||||
|
||||
// Make the plus button square.
|
||||
if (opt->tabIndex + 1 == tab_bar->count())
|
||||
size.setWidth(size.height());
|
||||
// Make the tabs a bit taller, otherwise there's an awkward margin.
|
||||
size.setHeight(size.height() + 4);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <QtWidgets/QProxyStyle>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTabBar>
|
||||
#include <QtWidgets/QBoxLayout>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class DockMenuBarStyle;
|
||||
@@ -36,8 +37,6 @@ public:
|
||||
void updateBlink();
|
||||
void stopBlink();
|
||||
|
||||
int innerHeight() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void currentLayoutChanged(DockLayout::Index layout_index);
|
||||
void newButtonClicked();
|
||||
@@ -54,6 +53,7 @@ private:
|
||||
|
||||
QWidget* m_original_menu_bar;
|
||||
|
||||
QVBoxLayout* m_layout_switcher_layout;
|
||||
QTabBar* m_layout_switcher;
|
||||
QMetaObject::Connection m_tab_connection;
|
||||
int m_plus_tab_index = -1;
|
||||
@@ -70,8 +70,7 @@ private:
|
||||
DockMenuBarStyle* m_style = nullptr;
|
||||
};
|
||||
|
||||
// Fixes some theming issues relating to the menu bar, the layout switcher and
|
||||
// the layout locked/unlocked toggle button.
|
||||
// Fixes some theming issues relating to the menu bar and the layout switcher.
|
||||
class DockMenuBarStyle : public QProxyStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -83,11 +82,11 @@ public:
|
||||
ControlElement element,
|
||||
const QStyleOption* option,
|
||||
QPainter* painter,
|
||||
const QWidget* widget = nullptr) const override;
|
||||
const QWidget* widget) const override;
|
||||
|
||||
QSize sizeFromContents(
|
||||
QStyle::ContentsType type,
|
||||
const QStyleOption* option,
|
||||
const QSize& contents_size,
|
||||
const QWidget* widget = nullptr) const override;
|
||||
const QWidget* widget) const override;
|
||||
};
|
||||
|
||||
@@ -114,12 +114,30 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
|
||||
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
|
||||
|
||||
QComboBox* combo_box = new QComboBox(parent);
|
||||
for (s32 i = 0; i < (s32)enumeration.constants.size(); i++)
|
||||
bool named = false;
|
||||
|
||||
for (size_t i = 0; i < enumeration.constants.size(); i++)
|
||||
{
|
||||
combo_box->addItem(QString::fromStdString(enumeration.constants[i].second));
|
||||
QString text = QString::fromStdString(enumeration.constants[i].second);
|
||||
combo_box->addItem(text, enumeration.constants[i].first);
|
||||
if (enumeration.constants[i].first == value.toInt())
|
||||
combo_box->setCurrentIndex(i);
|
||||
{
|
||||
combo_box->setCurrentIndex(static_cast<int>(i));
|
||||
named = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!named)
|
||||
{
|
||||
// The value isn't equal to any of the named constants, so
|
||||
// add an extra item to the combo box representing the
|
||||
// current value so that the first named constant isn't
|
||||
// written back to VM memory accidentally.
|
||||
QString text = display_options.signedIntegerToString(value.toInt(), 32);
|
||||
combo_box->insertItem(0, text, value.toInt());
|
||||
combo_box->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
connect(combo_box, &QComboBox::currentIndexChanged, this, &SymbolTreeValueDelegate::onComboBoxIndexChanged);
|
||||
result = combo_box;
|
||||
|
||||
@@ -245,15 +263,10 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel*
|
||||
}
|
||||
case ccc::ast::ENUM:
|
||||
{
|
||||
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
|
||||
QComboBox* combo_box = qobject_cast<QComboBox*>(editor);
|
||||
Q_ASSERT(combo_box);
|
||||
|
||||
s32 comboIndex = combo_box->currentIndex();
|
||||
if (comboIndex < 0 || comboIndex >= (s32)enumeration.constants.size())
|
||||
break;
|
||||
|
||||
value = enumeration.constants[comboIndex].first;
|
||||
value = combo_box->currentData().toInt();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -390,15 +390,13 @@ QString SymbolTreeNode::generateDisplayString(
|
||||
}
|
||||
case ccc::ast::ENUM:
|
||||
{
|
||||
s32 value = (s32)location.read32(cpu);
|
||||
s32 value = static_cast<s32>(location.read32(cpu));
|
||||
const auto& enum_type = physical_type.as<ccc::ast::Enum>();
|
||||
for (auto [test_value, name] : enum_type.constants)
|
||||
{
|
||||
for (const auto& [test_value, name] : enum_type.constants)
|
||||
if (test_value == value)
|
||||
return QString::fromStdString(name);
|
||||
}
|
||||
|
||||
break;
|
||||
return display_options.signedIntegerToString(value, 32);
|
||||
}
|
||||
case ccc::ast::POINTER_OR_REFERENCE:
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent)
|
||||
|
||||
int ThreadModel::rowCount(const QModelIndex&) const
|
||||
{
|
||||
return m_cpu.GetThreadList().size();
|
||||
return static_cast<int>(m_threads.size());
|
||||
}
|
||||
|
||||
int ThreadModel::columnCount(const QModelIndex&) const
|
||||
|
||||
@@ -29,20 +29,12 @@
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
DisplayWidget::DisplayWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
DisplaySurface::DisplaySurface()
|
||||
: QWindow()
|
||||
{
|
||||
// We want a native window for both D3D and OpenGL.
|
||||
setAutoFillBackground(false);
|
||||
setAttribute(Qt::WA_NativeWindow, true);
|
||||
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||
setAttribute(Qt::WA_KeyCompression, false);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
DisplayWidget::~DisplayWidget()
|
||||
DisplaySurface::~DisplaySurface()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
@@ -50,19 +42,17 @@ DisplayWidget::~DisplayWidget()
|
||||
#endif
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowWidth() const
|
||||
QWidget* DisplaySurface::createWindowContainer(QWidget* parent)
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioF())), 1);
|
||||
m_container = QWidget::createWindowContainer(this, parent);
|
||||
m_container->installEventFilter(this);
|
||||
m_container->setFocusPolicy(Qt::StrongFocus);
|
||||
return m_container;
|
||||
}
|
||||
|
||||
int DisplayWidget::scaledWindowHeight() const
|
||||
std::optional<WindowInfo> DisplaySurface::getWindowInfo()
|
||||
{
|
||||
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioF())), 1);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
{
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this));
|
||||
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWindow(this));
|
||||
if (ret.has_value())
|
||||
{
|
||||
m_last_window_width = ret->surface_width;
|
||||
@@ -72,7 +62,7 @@ std::optional<WindowInfo> DisplayWidget::getWindowInfo()
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DisplayWidget::updateRelativeMode(bool enabled)
|
||||
void DisplaySurface::updateRelativeMode(bool enabled)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// prefer ClipCursor() over warping movement when we're using raw input
|
||||
@@ -104,17 +94,17 @@ void DisplayWidget::updateRelativeMode(bool enabled)
|
||||
#endif
|
||||
m_relative_mouse_start_pos = QCursor::pos();
|
||||
updateCenterPos();
|
||||
grabMouse();
|
||||
setMouseGrabEnabled(true);
|
||||
}
|
||||
else if (m_relative_mouse_enabled)
|
||||
{
|
||||
m_relative_mouse_enabled = false;
|
||||
QCursor::setPos(m_relative_mouse_start_pos);
|
||||
releaseMouse();
|
||||
setMouseGrabEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCursor(bool hidden)
|
||||
void DisplaySurface::updateCursor(bool hidden)
|
||||
{
|
||||
if (m_cursor_hidden == hidden)
|
||||
return;
|
||||
@@ -132,7 +122,7 @@ void DisplayWidget::updateCursor(bool hidden)
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWidget::handleCloseEvent(QCloseEvent* event)
|
||||
void DisplaySurface::handleCloseEvent(QCloseEvent* event)
|
||||
{
|
||||
// Closing the separate widget will either cancel the close, or trigger shutdown.
|
||||
// In the latter case, it's going to destroy us, so don't let Qt do it first.
|
||||
@@ -152,28 +142,13 @@ void DisplayWidget::handleCloseEvent(QCloseEvent* event)
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void DisplayWidget::destroy()
|
||||
bool DisplaySurface::isActuallyFullscreen() const
|
||||
{
|
||||
m_destroying = true;
|
||||
|
||||
#ifdef __APPLE__
|
||||
// See Qt documentation, entire application is in full screen state, and the main
|
||||
// window will get reopened fullscreen instead of windowed if we don't close the
|
||||
// fullscreen window first.
|
||||
if (isActuallyFullscreen())
|
||||
close();
|
||||
#endif
|
||||
deleteLater();
|
||||
// DisplaySurface is always in a container, so we need to check parent window
|
||||
return parent()->windowState() & Qt::WindowFullScreen;
|
||||
}
|
||||
|
||||
bool DisplayWidget::isActuallyFullscreen() const
|
||||
{
|
||||
// I hate you QtWayland... have to check the parent, not ourselves.
|
||||
QWidget* container = qobject_cast<QWidget*>(parent());
|
||||
return container ? container->isFullScreen() : isFullScreen();
|
||||
}
|
||||
|
||||
void DisplayWidget::updateCenterPos()
|
||||
void DisplaySurface::updateCenterPos()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_clip_mouse_enabled)
|
||||
@@ -203,12 +178,14 @@ void DisplayWidget::updateCenterPos()
|
||||
#endif
|
||||
}
|
||||
|
||||
QPaintEngine* DisplayWidget::paintEngine() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool DisplayWidget::event(QEvent* event)
|
||||
// Keyboard focus and child windows are inconsistant across platforms;
|
||||
// Windows: Can programmatically focus the child window, NVidia overlay can defocus it.
|
||||
// X11: Can programmatically focus the child window.
|
||||
// Wayland: Child window cannot be focused at all on most(?) DE.
|
||||
// Mac: Can programmatically focus the child window.
|
||||
// Thus for KB inputs we need to sometimes use the event filter.
|
||||
// Mouse events are always delivered to the child window, so that seems consistant.
|
||||
void DisplaySurface::handleKeyInputEvent(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
@@ -229,7 +206,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
}
|
||||
|
||||
if (key_event->isAutoRepeat())
|
||||
return true;
|
||||
return;
|
||||
|
||||
// For some reason, Windows sends "fake" key events.
|
||||
// Scenario: Press shift, press F1, release shift, release F1.
|
||||
@@ -246,7 +223,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
if (it != m_keys_pressed_with_modifiers.end())
|
||||
{
|
||||
if (pressed)
|
||||
return true;
|
||||
return;
|
||||
else
|
||||
m_keys_pressed_with_modifiers.erase(it);
|
||||
}
|
||||
@@ -259,6 +236,23 @@ bool DisplayWidget::event(QEvent* event)
|
||||
InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), static_cast<float>(pressed));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
pxAssert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool DisplaySurface::event(QEvent* event)
|
||||
{
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
{
|
||||
handleKeyInputEvent(event);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -268,7 +262,7 @@ bool DisplayWidget::event(QEvent* event)
|
||||
|
||||
if (!m_relative_mouse_enabled)
|
||||
{
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
const qreal dpr = devicePixelRatio();
|
||||
const QPoint mouse_pos = mouse_event->pos();
|
||||
|
||||
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
|
||||
@@ -352,11 +346,11 @@ bool DisplayWidget::event(QEvent* event)
|
||||
case QEvent::DevicePixelRatioChange:
|
||||
case QEvent::Resize:
|
||||
{
|
||||
QWidget::event(event);
|
||||
QWindow::event(event);
|
||||
|
||||
const float dpr = devicePixelRatioF();
|
||||
const u32 scaled_width = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * dpr)), 1));
|
||||
const float dpr = devicePixelRatio();
|
||||
const u32 scaled_width = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(width()) * dpr)), 1));
|
||||
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(height()) * dpr)), 1));
|
||||
|
||||
// avoid spamming resize events for paint events (sent on move on windows)
|
||||
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
|
||||
@@ -371,106 +365,60 @@ bool DisplayWidget::event(QEvent* event)
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::DragEnter:
|
||||
QWindow::event(event);
|
||||
emit dragEnterEvent(static_cast<QDragEnterEvent*>(event));
|
||||
return event->isAccepted();
|
||||
|
||||
case QEvent::Drop:
|
||||
QWindow::event(event);
|
||||
emit dropEvent(static_cast<QDropEvent*>(event));
|
||||
return event->isAccepted();
|
||||
|
||||
case QEvent::Move:
|
||||
{
|
||||
updateCenterPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::Close:
|
||||
{
|
||||
if (m_destroying)
|
||||
return QWidget::event(event);
|
||||
|
||||
handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
QWidget::event(event);
|
||||
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit windowRestoredEvent();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
return QWidget::event(event);
|
||||
return QWindow::event(event);
|
||||
}
|
||||
}
|
||||
|
||||
DisplayContainer::DisplayContainer()
|
||||
: QStackedWidget(nullptr)
|
||||
bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
}
|
||||
|
||||
DisplayContainer::~DisplayContainer() = default;
|
||||
|
||||
bool DisplayContainer::isNeeded(bool fullscreen, bool render_to_main)
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
if (!isRunningOnWayland())
|
||||
return false;
|
||||
|
||||
// We only need this on Wayland because of client-side decorations...
|
||||
return (fullscreen || !render_to_main);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DisplayContainer::isRunningOnWayland()
|
||||
{
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
return false;
|
||||
#else
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
return (platform_name == QStringLiteral("wayland"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void DisplayContainer::setDisplayWidget(DisplayWidget* widget)
|
||||
{
|
||||
pxAssert(!m_display_widget);
|
||||
m_display_widget = widget;
|
||||
addWidget(widget);
|
||||
}
|
||||
|
||||
DisplayWidget* DisplayContainer::removeDisplayWidget()
|
||||
{
|
||||
DisplayWidget* widget = m_display_widget;
|
||||
pxAssert(widget);
|
||||
m_display_widget = nullptr;
|
||||
removeWidget(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
bool DisplayContainer::event(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::Close && m_display_widget)
|
||||
{
|
||||
m_display_widget->handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool res = QStackedWidget::event(event);
|
||||
if (!m_display_widget)
|
||||
return res;
|
||||
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
#ifdef _WIN32
|
||||
// Nvidia overlay causes the child window to lose focus, but not its parent.
|
||||
// Refocus the child window.
|
||||
requestActivate();
|
||||
#endif
|
||||
handleKeyInputEvent(event);
|
||||
return true;
|
||||
|
||||
// These events only work on the top level control.
|
||||
// Which is this container when render to seperate or fullscreen is active.
|
||||
case QEvent::Close:
|
||||
handleCloseEvent(static_cast<QCloseEvent*>(event));
|
||||
return true;
|
||||
case QEvent::WindowStateChange:
|
||||
{
|
||||
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
|
||||
emit m_display_widget->windowRestoredEvent();
|
||||
}
|
||||
break;
|
||||
emit windowRestoredEvent();
|
||||
return false;
|
||||
|
||||
case QEvent::ChildRemoved:
|
||||
if (static_cast<QChildEvent*>(event)->child() == m_container)
|
||||
{
|
||||
object->removeEventFilter(this);
|
||||
m_container = nullptr;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -3,40 +3,44 @@
|
||||
|
||||
#pragma once
|
||||
#include "common/WindowInfo.h"
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QtGui/QDragMoveEvent>
|
||||
#include <QtGui/QWindow>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
class QCloseEvent;
|
||||
|
||||
class DisplayWidget final : public QWidget
|
||||
class DisplaySurface final : public QWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DisplayWidget(QWidget* parent);
|
||||
~DisplayWidget();
|
||||
explicit DisplaySurface();
|
||||
~DisplaySurface();
|
||||
|
||||
QPaintEngine* paintEngine() const override;
|
||||
|
||||
int scaledWindowWidth() const;
|
||||
int scaledWindowHeight() const;
|
||||
// while QWindow can be used directly as a window, Popups requre a QWidget parent.
|
||||
// Additionally, we use saveGeometry/restoreGeometry for render to seperate window mode
|
||||
// but those functions only exist on QWidget.
|
||||
// Thus, we always need a container widget.
|
||||
QWidget* createWindowContainer(QWidget* parent = nullptr);
|
||||
|
||||
std::optional<WindowInfo> getWindowInfo();
|
||||
|
||||
void updateRelativeMode(bool enabled);
|
||||
void updateCursor(bool hidden);
|
||||
|
||||
void handleCloseEvent(QCloseEvent* event);
|
||||
void destroy();
|
||||
|
||||
Q_SIGNALS:
|
||||
void windowResizedEvent(int width, int height, float scale);
|
||||
void windowRestoredEvent();
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent* event);
|
||||
void dropEvent(QDropEvent* event);
|
||||
|
||||
protected:
|
||||
void handleCloseEvent(QCloseEvent* event);
|
||||
void handleKeyInputEvent(QEvent* event);
|
||||
bool event(QEvent* event) override;
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
private:
|
||||
bool isActuallyFullscreen() const;
|
||||
@@ -49,34 +53,12 @@ private:
|
||||
bool m_clip_mouse_enabled = false;
|
||||
#endif
|
||||
bool m_cursor_hidden = false;
|
||||
bool m_destroying = false;
|
||||
|
||||
std::vector<int> m_keys_pressed_with_modifiers;
|
||||
|
||||
u32 m_last_window_width = 0;
|
||||
u32 m_last_window_height = 0;
|
||||
float m_last_window_scale = 1.0f;
|
||||
};
|
||||
|
||||
class DisplayContainer final : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DisplayContainer();
|
||||
~DisplayContainer();
|
||||
|
||||
// Wayland is broken in lots of ways, so we need to check for it.
|
||||
static bool isRunningOnWayland();
|
||||
|
||||
static bool isNeeded(bool fullscreen, bool render_to_main);
|
||||
|
||||
void setDisplayWidget(DisplayWidget* widget);
|
||||
DisplayWidget* removeDisplayWidget();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
|
||||
QWidget* m_container = nullptr;
|
||||
};
|
||||
|
||||
@@ -31,60 +31,15 @@ static constexpr int SIZE_HINT_HEIGHT_TITLES = SIZE_HINT_HEIGHT + COVER_ART_SPAC
|
||||
|
||||
static constexpr int MIN_COVER_CACHE_SIZE = 256;
|
||||
|
||||
static int DPRScale(const int size, const qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) * dpr);
|
||||
}
|
||||
|
||||
static int DPRUnscale(const int size, const qreal dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<qreal>(size) / dpr);
|
||||
}
|
||||
|
||||
static void resizeAndPadPixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr)
|
||||
{
|
||||
const int dpr_expected_width = DPRScale(expected_width, dpr);
|
||||
const int dpr_expected_height = DPRScale(expected_height, dpr);
|
||||
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
*pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// QPainter works in unscaled coordinates.
|
||||
int xoffs = 0;
|
||||
int yoffs = 0;
|
||||
if (pm->width() < dpr_expected_width)
|
||||
xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr);
|
||||
if (pm->height() < dpr_expected_height)
|
||||
yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr);
|
||||
|
||||
QPixmap padded_image(dpr_expected_width, dpr_expected_height);
|
||||
padded_image.setDevicePixelRatio(dpr);
|
||||
padded_image.fill(Qt::transparent);
|
||||
QPainter painter;
|
||||
if (painter.begin(&padded_image))
|
||||
{
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawPixmap(xoffs, yoffs, *pm);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Destination);
|
||||
painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
|
||||
painter.end();
|
||||
}
|
||||
|
||||
*pm = padded_image;
|
||||
}
|
||||
|
||||
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, const int width, const int height,
|
||||
const float scale, const qreal dpr, const std::string& title)
|
||||
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
|
||||
qreal dpr, const std::string& title)
|
||||
{
|
||||
QPixmap pm(placeholder_pixmap.copy());
|
||||
pm.setDevicePixelRatio(dpr);
|
||||
if (pm.isNull())
|
||||
return QPixmap();
|
||||
|
||||
resizeAndPadPixmap(&pm, width, height, dpr);
|
||||
QtUtils::resizeAndScalePixmap(&pm, width, height, dpr, QtUtils::ScalingMode::Fit, 100);
|
||||
QPainter painter;
|
||||
if (painter.begin(&pm))
|
||||
{
|
||||
@@ -199,7 +154,7 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
|
||||
else
|
||||
{
|
||||
image.setDevicePixelRatio(m_dpr);
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
|
||||
QtUtils::resizeAndScalePixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr, QtUtils::ScalingMode::Fit, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
#include "GameListRefreshThread.h"
|
||||
|
||||
#include "pcsx2/GameList.h"
|
||||
#include "pcsx2/Host.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/ProgressCallback.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent)
|
||||
AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent)
|
||||
: m_parent(parent)
|
||||
, m_popup_on_error(popup_on_error)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,17 +60,20 @@ void AsyncRefreshProgressCallback::SetTitle(const char* title)
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
if (m_popup_on_error)
|
||||
Host::ReportErrorAsync(TRANSLATE_SV("GameListRefreshThread", "Error"), message);
|
||||
else
|
||||
ERROR_LOG("{}", message);
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayWarning(const char* message)
|
||||
{
|
||||
QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
|
||||
@@ -77,17 +83,18 @@ void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalError(const char* message)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message)
|
||||
{
|
||||
return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes;
|
||||
pxFailRel("Not implemented.");
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::ModalInformation(const char* message)
|
||||
{
|
||||
QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
|
||||
pxFailRel("Not implemented.");
|
||||
}
|
||||
|
||||
void AsyncRefreshProgressCallback::fireUpdate()
|
||||
@@ -95,9 +102,9 @@ void AsyncRefreshProgressCallback::fireUpdate()
|
||||
m_parent->refreshProgress(m_status_text, m_last_value, m_last_range);
|
||||
}
|
||||
|
||||
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
|
||||
GameListRefreshThread::GameListRefreshThread(bool invalidate_cache, bool popup_on_error)
|
||||
: QThread()
|
||||
, m_progress(this)
|
||||
, m_progress(popup_on_error, this)
|
||||
, m_invalidate_cache(invalidate_cache)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class GameListRefreshThread;
|
||||
class AsyncRefreshProgressCallback : public BaseProgressCallback
|
||||
{
|
||||
public:
|
||||
AsyncRefreshProgressCallback(GameListRefreshThread* parent);
|
||||
AsyncRefreshProgressCallback(bool popup_on_error, GameListRefreshThread* parent);
|
||||
|
||||
void Cancel();
|
||||
|
||||
@@ -38,6 +38,7 @@ private:
|
||||
QString m_status_text;
|
||||
int m_last_range = 1;
|
||||
int m_last_value = 0;
|
||||
bool m_popup_on_error = false;
|
||||
};
|
||||
|
||||
class GameListRefreshThread final : public QThread
|
||||
@@ -45,7 +46,7 @@ class GameListRefreshThread final : public QThread
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListRefreshThread(bool invalidate_cache);
|
||||
GameListRefreshThread(bool invalidate_cache, bool popup_on_error);
|
||||
~GameListRefreshThread();
|
||||
|
||||
void cancel();
|
||||
@@ -60,4 +61,5 @@ protected:
|
||||
private:
|
||||
AsyncRefreshProgressCallback m_progress;
|
||||
bool m_invalidate_cache;
|
||||
bool m_popup_on_error;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "QtHost.h"
|
||||
#include "QtUtils.h"
|
||||
|
||||
#include "Settings/InterfaceSettingsWidget.h"
|
||||
#include "pcsx2/GameList.h"
|
||||
#include "pcsx2/Host.h"
|
||||
|
||||
@@ -44,6 +45,25 @@ static const char* SUPPORTED_FORMATS_STRING = QT_TRANSLATE_NOOP(GameListWidget,
|
||||
static constexpr float MIN_SCALE = 0.1f;
|
||||
static constexpr float MAX_SCALE = 2.0f;
|
||||
|
||||
static constexpr GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Title;
|
||||
static constexpr int DEFAULT_SORT_INDEX = static_cast<int>(DEFAULT_SORT_COLUMN);
|
||||
static constexpr Qt::SortOrder DEFAULT_SORT_ORDER = Qt::AscendingOrder;
|
||||
|
||||
static constexpr std::array<int, GameListModel::Column_Count> DEFAULT_COLUMN_WIDTHS = {{
|
||||
55, // type
|
||||
85, // code
|
||||
-1, // title
|
||||
-1, // file title
|
||||
75, // crc
|
||||
95, // time played
|
||||
90, // last played
|
||||
80, // size
|
||||
60, // region
|
||||
120 // compatibility
|
||||
}};
|
||||
static_assert(static_cast<int>(DEFAULT_COLUMN_WIDTHS.size()) <= GameListModel::Column_Count,
|
||||
"Game List: More default column widths than column types.");
|
||||
|
||||
class GameListSortModel final : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
@@ -55,18 +75,21 @@ public:
|
||||
|
||||
void setFilterType(GameList::EntryType type)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_type = type;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
void setFilterRegion(GameList::Region region)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_region = region;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
void setFilterName(const QString& name)
|
||||
{
|
||||
beginFilterChange();
|
||||
m_filter_name = name;
|
||||
invalidateRowsFilter();
|
||||
endFilterChange(Direction::Rows);
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
|
||||
@@ -106,6 +129,7 @@ private:
|
||||
|
||||
namespace
|
||||
{
|
||||
// Used for Type, Region, and Compatibility columns to center icons; Qt::AlignCenter only works on DisplayRole (text).
|
||||
class GameListIconStyleDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
@@ -115,62 +139,61 @@ namespace
|
||||
}
|
||||
~GameListIconStyleDelegate() = default;
|
||||
|
||||
// See: QStyledItemDelegate::paint(), QItemDelegate::drawDecoration(), and Qt::QStyleOptionViewItem.
|
||||
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
||||
{
|
||||
// https://stackoverflow.com/questions/32216568/how-to-set-icon-center-in-qtableview
|
||||
Q_ASSERT(index.isValid());
|
||||
|
||||
// Draw the base item, with a blank icon
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
opt.icon = QIcon();
|
||||
// Based on QStyledItemDelegate::paint()
|
||||
const QStyle* style = option.widget ? option.widget->style() : QApplication::style();
|
||||
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, option.widget);
|
||||
// Draw highlight for cell.
|
||||
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
|
||||
|
||||
// Fetch icon pixmap
|
||||
const QRect r = option.rect;
|
||||
const QPixmap pix = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||
// Fetch icon pixmap and stop if no icon exists
|
||||
const QPixmap icon = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
|
||||
|
||||
if (pix.isNull())
|
||||
if (icon.isNull())
|
||||
return;
|
||||
|
||||
const int pix_width = static_cast<int>(pix.width() / pix.devicePixelRatio());
|
||||
const int pix_height = static_cast<int>(pix.height() / pix.devicePixelRatio());
|
||||
|
||||
// Clip the pixmaps so they don't extend outside the column
|
||||
// Save painter state and restore later so clip setting doesn't persist across cell draws.
|
||||
painter->save();
|
||||
painter->setClipRect(option.rect);
|
||||
|
||||
// Draw the icon, using code derived from QItemDelegate::drawDecoration()
|
||||
const bool enabled = option.state & QStyle::State_Enabled;
|
||||
const QPoint p = QPoint((r.width() - pix_width) / 2, (r.height() - pix_height) / 2);
|
||||
// Clip pixmap so it doesn't extend outside the cell.
|
||||
const QRect rect = option.rect;
|
||||
painter->setClipRect(rect);
|
||||
|
||||
// Determine starting location of icon (Qt uses top-left origin).
|
||||
const int icon_width = static_cast<int>(static_cast<qreal>(icon.width()) / icon.devicePixelRatio());
|
||||
const int icon_height = static_cast<int>(static_cast<qreal>(icon.height()) / icon.devicePixelRatio());
|
||||
const QPoint icon_top_left = QPoint((rect.width() - icon_width) / 2, (rect.height() - icon_height) / 2);
|
||||
|
||||
// Change palette if the item is selected.
|
||||
if (option.state & QStyle::State_Selected)
|
||||
{
|
||||
// See QItemDelegate::selectedPixmap()
|
||||
// Set color based on whether cell is enabled.
|
||||
const bool enabled = option.state & QStyle::State_Enabled;
|
||||
QColor color = option.palette.color(enabled ? QPalette::Normal : QPalette::Disabled, QPalette::Highlight);
|
||||
color.setAlphaF(0.3f);
|
||||
|
||||
QString key = QString::fromStdString(fmt::format("{:016X}-{:d}-{:08X}", pix.cacheKey(), enabled, color.rgba()));
|
||||
QPixmap pm;
|
||||
if (!QPixmapCache::find(key, &pm))
|
||||
// Fetch pixmap from cache or construct a new one.
|
||||
const QString key = QString::fromStdString(fmt::format("{:016X}-{:d}-{:08X}", icon.cacheKey(), enabled, color.rgba()));
|
||||
QPixmap highlighted_icon;
|
||||
if (!QPixmapCache::find(key, &highlighted_icon))
|
||||
{
|
||||
QImage img = pix.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
QImage img = icon.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
|
||||
QPainter tinted_painter(&img);
|
||||
tinted_painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
|
||||
tinted_painter.fillRect(0, 0, img.width(), img.height(), color);
|
||||
tinted_painter.end();
|
||||
|
||||
pm = QPixmap(QPixmap::fromImage(img));
|
||||
QPixmapCache::insert(key, pm);
|
||||
highlighted_icon = QPixmap(QPixmap::fromImage(img));
|
||||
QPixmapCache::insert(key, highlighted_icon);
|
||||
}
|
||||
|
||||
painter->drawPixmap(r.topLeft() + p, pm);
|
||||
painter->drawPixmap(rect.topLeft() + icon_top_left, highlighted_icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->drawPixmap(r.topLeft() + p, pix);
|
||||
painter->drawPixmap(rect.topLeft() + icon_top_left, icon);
|
||||
}
|
||||
|
||||
// Restore the old clip path.
|
||||
@@ -234,33 +257,46 @@ void GameListWidget::initialize()
|
||||
m_table_view = new QTableView(m_ui.stack);
|
||||
m_table_view->setModel(m_sort_model);
|
||||
m_table_view->setSortingEnabled(true);
|
||||
m_table_view->horizontalHeader()->setSectionsMovable(true);
|
||||
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->setAlternatingRowColors(true);
|
||||
m_table_view->setMouseTracking(true);
|
||||
m_table_view->setShowGrid(false);
|
||||
m_table_view->setCurrentIndex({});
|
||||
m_table_view->setCurrentIndex(QModelIndex());
|
||||
m_table_view->horizontalHeader()->setHighlightSections(false);
|
||||
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_table_view->verticalHeader()->hide();
|
||||
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
|
||||
|
||||
// Custom painter to center-align DisplayRoles (icons)
|
||||
m_table_view->setItemDelegateForColumn(0, new GameListIconStyleDelegate(this));
|
||||
m_table_view->setItemDelegateForColumn(8, new GameListIconStyleDelegate(this));
|
||||
m_table_view->setItemDelegateForColumn(9, new GameListIconStyleDelegate(this));
|
||||
|
||||
loadTableViewColumnVisibilitySettings();
|
||||
loadTableViewColumnSortSettings();
|
||||
|
||||
connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
&GameListWidget::onSelectionModelCurrentChanged);
|
||||
connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
|
||||
connect(m_table_view, &QTableView::customContextMenuRequested, this,
|
||||
&GameListWidget::onTableViewContextMenuRequested);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
|
||||
&GameListWidget::onTableViewHeaderContextMenuRequested);
|
||||
&GameListWidget::onTableViewHeaderContextMenuRequested);
|
||||
|
||||
// Save state when header state changes (hiding and showing handled within onTableViewHeaderContextMenuRequested).
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionMoved, this, &GameListWidget::onTableHeaderStateChanged);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sectionResized, this, &GameListWidget::onTableHeaderStateChanged);
|
||||
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
|
||||
&GameListWidget::saveTableViewColumnSortSettings);
|
||||
[this](const int column, const Qt::SortOrder sort_order) { GameListWidget::saveSortSettings(column, sort_order); GameListWidget::onTableHeaderStateChanged(); });
|
||||
|
||||
// Load the last session's header state or create a new one.
|
||||
if (Host::ContainsBaseSettingValue("GameListTableView", "HeaderState"))
|
||||
loadTableHeaderState();
|
||||
else
|
||||
applyTableHeaderDefaults();
|
||||
|
||||
// After header state load to account for user-specified sort.
|
||||
m_table_view->setSortingEnabled(true);
|
||||
|
||||
m_ui.stack->insertWidget(0, m_table_view);
|
||||
|
||||
@@ -292,7 +328,7 @@ void GameListWidget::initialize()
|
||||
m_empty_ui.setupUi(m_empty_widget);
|
||||
m_empty_ui.supportedFormats->setText(qApp->translate("GameListWidget", SUPPORTED_FORMATS_STRING));
|
||||
connect(m_empty_ui.addGameDirectory, &QPushButton::clicked, this, [this]() { emit addGameDirectoryRequested(); });
|
||||
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false); });
|
||||
connect(m_empty_ui.scanForNewGames, &QPushButton::clicked, this, [this]() { refresh(false, true); });
|
||||
connect(qApp, &QGuiApplication::applicationStateChanged, this, [this]() { GameListWidget::updateCustomBackgroundState(); });
|
||||
m_ui.stack->insertWidget(2, m_empty_widget);
|
||||
|
||||
@@ -308,84 +344,30 @@ void GameListWidget::initialize()
|
||||
setCustomBackground();
|
||||
}
|
||||
|
||||
static void resizeAndPadImage(QImage* image, int expected_width, int expected_height, bool fill_with_top_left, bool expand_to_fill)
|
||||
void GameListWidget::setCustomBackground()
|
||||
{
|
||||
const qreal dpr = image->devicePixelRatio();
|
||||
const int dpr_expected_width = static_cast<int>(static_cast<qreal>(expected_width) * dpr);
|
||||
const int dpr_expected_height = static_cast<int>(static_cast<qreal>(expected_height) * dpr);
|
||||
if (image->width() == dpr_expected_width && image->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// Resize
|
||||
if (((static_cast<float>(image->width()) / static_cast<float>(image->height())) >=
|
||||
(static_cast<float>(dpr_expected_width) / static_cast<float>(dpr_expected_height))) != expand_to_fill)
|
||||
{
|
||||
*image = image->scaledToWidth(dpr_expected_width, Qt::SmoothTransformation);
|
||||
}
|
||||
else
|
||||
{
|
||||
*image = image->scaledToHeight(dpr_expected_height, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
if (image->width() == dpr_expected_width && image->height() == dpr_expected_height)
|
||||
return;
|
||||
|
||||
// Padding
|
||||
int xoffs = 0;
|
||||
int yoffs = 0;
|
||||
const int image_width = image->width();
|
||||
const int image_height = image->height();
|
||||
if ((image_width < dpr_expected_width) != expand_to_fill)
|
||||
xoffs = static_cast<int>(static_cast<qreal>((dpr_expected_width - image_width) / 2) / dpr);
|
||||
if ((image_height < dpr_expected_height) != expand_to_fill)
|
||||
yoffs = static_cast<int>(static_cast<qreal>((dpr_expected_height - image_height) / 2) / dpr);
|
||||
|
||||
QImage padded_image(dpr_expected_width, dpr_expected_height, QImage::Format_ARGB32);
|
||||
padded_image.setDevicePixelRatio(dpr);
|
||||
if (fill_with_top_left)
|
||||
padded_image.fill(image->pixel(0, 0));
|
||||
else
|
||||
padded_image.fill(Qt::transparent);
|
||||
|
||||
// Painting
|
||||
QPainter painter;
|
||||
const float opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity");
|
||||
if (painter.begin(&padded_image))
|
||||
{
|
||||
painter.setOpacity((static_cast<float>(opacity / 100.0f))); // Qt expects the range to be from 0.0 to 1.0
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawImage(xoffs, yoffs, *image);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
*image = std::move(padded_image);
|
||||
}
|
||||
|
||||
void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
{
|
||||
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
|
||||
bool enabled = Host::GetBaseBoolSettingValue("UI", "GameListBackgroundEnabled");
|
||||
bool fill = Host::GetBaseBoolSettingValue("UI", "GameListBackgroundFill");
|
||||
|
||||
// Cleanup old animation if it still exists on gamelist
|
||||
if (m_background_movie != nullptr)
|
||||
{
|
||||
m_background_movie->disconnect(this);
|
||||
delete m_background_movie;
|
||||
m_background_movie = nullptr;
|
||||
}
|
||||
|
||||
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
|
||||
if (!Path::IsAbsolute(path))
|
||||
path = Path::Combine(EmuFolders::DataRoot, path);
|
||||
|
||||
// Only try to create background both if path are valid and custom background are enabled
|
||||
if ((!path.empty() && FileSystem::FileExists(path.c_str())) && enabled)
|
||||
// Only try to create background if path are valid
|
||||
if (!path.empty() && FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
QMovie* new_movie;
|
||||
if (Path::GetExtension(path) == "png")
|
||||
QString img_path = QString::fromStdString(path);
|
||||
if (img_path.endsWith(".png", Qt::CaseInsensitive))
|
||||
// Use apng plugin
|
||||
new_movie = new QMovie(QString::fromStdString(path), "apng", this);
|
||||
new_movie = new QMovie(img_path, "apng", this);
|
||||
else
|
||||
new_movie = new QMovie(QString::fromStdString(path), QByteArray(), this);
|
||||
new_movie = new QMovie(img_path, QByteArray(), this);
|
||||
|
||||
if (new_movie->isValid())
|
||||
m_background_movie = new_movie;
|
||||
@@ -396,7 +378,7 @@ void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no valid background then reset fallback to UI state
|
||||
// If there is no valid background then reset fallback to default UI state
|
||||
if (!m_background_movie)
|
||||
{
|
||||
m_ui.stack->setPalette(QApplication::palette());
|
||||
@@ -404,36 +386,60 @@ void GameListWidget::setCustomBackground(bool force_refresh)
|
||||
return;
|
||||
}
|
||||
|
||||
// Background is valid, connect the signals and start animation in gamelist
|
||||
connect(m_background_movie, &QMovie::frameChanged, this, [this, fill]() { processBackgroundFrames(fill); });
|
||||
updateCustomBackgroundState(force_refresh);
|
||||
// Retrieve scaling setting
|
||||
m_background_scaling = QtUtils::ScalingMode::Fit;
|
||||
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
|
||||
for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++)
|
||||
{
|
||||
if (!(InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] == nullptr))
|
||||
{
|
||||
if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i])
|
||||
{
|
||||
m_background_scaling = static_cast<QtUtils::ScalingMode>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve opacity setting
|
||||
m_background_opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f);
|
||||
|
||||
// Selected Custom background is valid, connect the signals and start animation in gamelist
|
||||
connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection);
|
||||
updateCustomBackgroundState(true);
|
||||
m_table_view->setAlternatingRowColors(false);
|
||||
}
|
||||
|
||||
void GameListWidget::updateCustomBackgroundState(bool force_start)
|
||||
void GameListWidget::updateCustomBackgroundState(const bool force_start)
|
||||
{
|
||||
if (m_background_movie)
|
||||
if (m_background_movie && m_background_movie->isValid())
|
||||
{
|
||||
if ((isVisible() && (isActiveWindow() || force_start)) && qGuiApp->applicationState() == Qt::ApplicationActive)
|
||||
m_background_movie->start();
|
||||
m_background_movie->setPaused(false);
|
||||
else
|
||||
m_background_movie->stop();
|
||||
m_background_movie->setPaused(true);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::processBackgroundFrames(bool fill_area)
|
||||
void GameListWidget::processBackgroundFrames()
|
||||
{
|
||||
QImage img = m_background_movie->currentImage();
|
||||
img.setDevicePixelRatio(devicePixelRatioF());
|
||||
const int widget_width = m_ui.stack->width();
|
||||
const int widget_height = m_ui.stack->height();
|
||||
if (m_background_movie && m_background_movie->isValid())
|
||||
{
|
||||
const int widget_width = m_ui.stack->width();
|
||||
const int widget_height = m_ui.stack->height();
|
||||
|
||||
resizeAndPadImage(&img, widget_width, widget_height, false, fill_area);
|
||||
if (widget_width <= 0 || widget_height <= 0)
|
||||
return;
|
||||
|
||||
QPalette new_palette(m_ui.stack->palette());
|
||||
new_palette.setBrush(QPalette::Base, img);
|
||||
m_ui.stack->setPalette(new_palette);
|
||||
QPixmap pm = m_background_movie->currentPixmap();
|
||||
const qreal dpr = devicePixelRatioF();
|
||||
|
||||
QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity);
|
||||
|
||||
QPalette bg_palette(m_ui.stack->palette());
|
||||
bg_palette.setBrush(QPalette::Base, pm);
|
||||
m_ui.stack->setPalette(bg_palette);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameList() const
|
||||
@@ -451,11 +457,11 @@ bool GameListWidget::getShowGridCoverTitles() const
|
||||
return m_model->getShowCoverTitles();
|
||||
}
|
||||
|
||||
void GameListWidget::refresh(bool invalidate_cache)
|
||||
void GameListWidget::refresh(bool invalidate_cache, bool popup_on_error)
|
||||
{
|
||||
cancelRefresh();
|
||||
|
||||
m_refresh_thread = new GameListRefreshThread(invalidate_cache);
|
||||
m_refresh_thread = new GameListRefreshThread(invalidate_cache, popup_on_error);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress,
|
||||
Qt::QueuedConnection);
|
||||
connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,
|
||||
@@ -552,18 +558,24 @@ void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
|
||||
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
QMenu menu;
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
int column_visual = 0;
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
// The "cover" column is the game grid and cannot be hidden.
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
|
||||
column_visual = header->visualIndex(column);
|
||||
QAction* action = menu.addAction(m_model->getColumnDisplayName(column_visual));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!m_table_view->isColumnHidden(column));
|
||||
connect(action, &QAction::toggled, [this, column](bool enabled) {
|
||||
m_table_view->setColumnHidden(column, !enabled);
|
||||
saveTableViewColumnVisibilitySettings(column);
|
||||
action->setChecked(!m_table_view->isColumnHidden(column_visual));
|
||||
connect(action, &QAction::toggled, [this, column_visual](bool enabled) {
|
||||
m_table_view->setColumnHidden(column_visual, !enabled);
|
||||
onTableHeaderStateChanged();
|
||||
resizeTableViewColumnsToFit();
|
||||
});
|
||||
}
|
||||
@@ -706,7 +718,7 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
|
||||
QWidget::resizeEvent(event);
|
||||
resizeTableViewColumnsToFit();
|
||||
m_model->updateCacheSize(width(), height());
|
||||
setCustomBackground();
|
||||
processBackgroundFrames();
|
||||
}
|
||||
|
||||
bool GameListWidget::event(QEvent* event)
|
||||
@@ -724,123 +736,141 @@ bool GameListWidget::event(QEvent* event)
|
||||
void GameListWidget::resizeTableViewColumnsToFit()
|
||||
{
|
||||
QtUtils::ResizeColumnsForTableView(m_table_view, {
|
||||
45, // type
|
||||
80, // code
|
||||
-1, // title
|
||||
-1, // file title
|
||||
65, // crc
|
||||
80, // time played
|
||||
80, // last played
|
||||
80, // size
|
||||
60, // region
|
||||
120 // compatibility
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Serial],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_FileTitle],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Type],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_CRC],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_TimePlayed],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_LastPlayed],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Size],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Region],
|
||||
DEFAULT_COLUMN_WIDTHS[GameListModel::Column_Compatibility],
|
||||
});
|
||||
}
|
||||
|
||||
static std::string getColumnVisibilitySettingsKeyName(int column)
|
||||
void GameListWidget::loadTableHeaderState()
|
||||
{
|
||||
return StringUtil::StdStringFromFormat("Show%s",
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
// Decode Base64 string from settings to QByteArray state.
|
||||
const std::string state_setting = Host::GetBaseStringSettingValue("GameListTableView", "HeaderState");
|
||||
if (state_setting.empty())
|
||||
return;
|
||||
|
||||
QSignalBlocker blocker(header);
|
||||
header->restoreState(QByteArray::fromBase64(QByteArray::fromStdString(state_setting)));
|
||||
}
|
||||
|
||||
void GameListWidget::onTableHeaderStateChanged()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
// Encode QByteArray state as Base64 string for storage.
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
void GameListWidget::applyTableHeaderDefaults()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
{
|
||||
QSignalBlocker blocker(header);
|
||||
header->hideSection(GameListModel::Column_FileTitle);
|
||||
header->hideSection(GameListModel::Column_CRC);
|
||||
header->hideSection(GameListModel::Column_Cover);
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
|
||||
}
|
||||
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
// TODO (Tech): Create a button for this in the minibar. Currently unused.
|
||||
void GameListWidget::resetTableHeaderToDefault()
|
||||
{
|
||||
QHeaderView* header = m_table_view->horizontalHeader();
|
||||
if (!header)
|
||||
return;
|
||||
|
||||
{
|
||||
QSignalBlocker blocker(header);
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
// Reset size, position, and visibility.
|
||||
header->resizeSection(column, DEFAULT_COLUMN_WIDTHS[column]);
|
||||
header->moveSection(header->visualIndex(column), column);
|
||||
header->setSectionHidden(column,
|
||||
column == GameListModel::Column_CRC || column == GameListModel::Column_FileTitle);
|
||||
}
|
||||
header->hideSection(GameListModel::Column_Cover);
|
||||
header->setSortIndicator(DEFAULT_SORT_INDEX, DEFAULT_SORT_ORDER);
|
||||
}
|
||||
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "HeaderState", header->saveState().toBase64());
|
||||
}
|
||||
|
||||
void GameListWidget::saveSortSettings(const int column, const Qt::SortOrder sort_order)
|
||||
{
|
||||
Host::SetBaseStringSettingValue("GameListTableView", "SortColumn",
|
||||
GameListModel::getColumnName(static_cast<GameListModel::Column>(column)));
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", static_cast<bool>(sort_order));
|
||||
}
|
||||
|
||||
void GameListWidget::loadTableViewColumnVisibilitySettings()
|
||||
std::optional<GameList::Entry> GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
static constexpr std::array<bool, GameListModel::Column_Count> DEFAULT_VISIBILITY = {{
|
||||
true, // type
|
||||
true, // code
|
||||
true, // title
|
||||
false, // file title
|
||||
false, // crc
|
||||
true, // time played
|
||||
true, // last played
|
||||
true, // size
|
||||
true, // region
|
||||
true // compatibility
|
||||
}};
|
||||
auto lock = GameList::GetLock();
|
||||
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = Host::GetBaseBoolSettingValue(
|
||||
"GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), DEFAULT_VISIBILITY[column]);
|
||||
m_table_view->setColumnHidden(column, !visible);
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnVisibilitySettings()
|
||||
{
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
|
||||
{
|
||||
const bool visible = !m_table_view->isColumnHidden(column);
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
void GameListWidget::loadTableViewColumnSortSettings()
|
||||
{
|
||||
const GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Type;
|
||||
const bool DEFAULT_SORT_DESCENDING = false;
|
||||
|
||||
const GameListModel::Column sort_column =
|
||||
GameListModel::getColumnIdForName(Host::GetBaseStringSettingValue("GameListTableView", "SortColumn"))
|
||||
.value_or(DEFAULT_SORT_COLUMN);
|
||||
const bool sort_descending =
|
||||
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
|
||||
const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder;
|
||||
m_sort_model->sort(sort_column, sort_order);
|
||||
if (QHeaderView* hv = m_table_view->horizontalHeader())
|
||||
hv->setSortIndicator(sort_column, sort_order);
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order)
|
||||
{
|
||||
if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
|
||||
{
|
||||
Host::SetBaseStringSettingValue(
|
||||
"GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast<GameListModel::Column>(sort_column)));
|
||||
}
|
||||
|
||||
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_order == Qt::DescendingOrder);
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
|
||||
const GameList::Entry* GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
const GameList::Entry* entry;
|
||||
if (m_ui.stack->currentIndex() == 0)
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_table_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndexList selected_rows = selection_model->selectedRows();
|
||||
if (selected_rows.empty())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]);
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
entry = GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_list_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex());
|
||||
if (!source_index.isValid())
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
|
||||
return GameList::GetEntryByIndex(source_index.row());
|
||||
entry = GameList::GetEntryByIndex(source_index.row());
|
||||
}
|
||||
|
||||
if (!entry)
|
||||
return std::nullopt;
|
||||
|
||||
// Copy the entry here instead of keeping the lock held to avoid deadlocks.
|
||||
return *entry;
|
||||
}
|
||||
|
||||
void GameListWidget::rescanFile(const std::string& path)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "QtUtils.h"
|
||||
#include "ui_EmptyGameListWidget.h"
|
||||
#include "ui_GameListWidget.h"
|
||||
|
||||
@@ -46,18 +47,18 @@ public:
|
||||
void initialize();
|
||||
void resizeTableViewColumnsToFit();
|
||||
|
||||
void refresh(bool invalidate_cache);
|
||||
void refresh(bool invalidate_cache, bool popup_on_error);
|
||||
void cancelRefresh();
|
||||
void reloadThemeSpecificImages();
|
||||
void setCustomBackground(bool force = false);
|
||||
void updateCustomBackgroundState(bool force_start = false);
|
||||
void processBackgroundFrames(bool fill_area);
|
||||
void setCustomBackground();
|
||||
void updateCustomBackgroundState(const bool force_start = false);
|
||||
void processBackgroundFrames();
|
||||
|
||||
bool isShowingGameList() const;
|
||||
bool isShowingGameGrid() const;
|
||||
bool getShowGridCoverTitles() const;
|
||||
|
||||
const GameList::Entry* getSelectedEntry() const;
|
||||
std::optional<GameList::Entry> getSelectedEntry() const;
|
||||
|
||||
/// Rescans a single file. NOTE: Happens on UI thread.
|
||||
void rescanFile(const std::string& path);
|
||||
@@ -84,6 +85,7 @@ private Q_SLOTS:
|
||||
void onListViewItemActivated(const QModelIndex& index);
|
||||
void onListViewContextMenuRequested(const QPoint& point);
|
||||
void onCoverScaleChanged();
|
||||
void onTableHeaderStateChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void showGameList();
|
||||
@@ -101,11 +103,10 @@ protected:
|
||||
bool event(QEvent* event) override;
|
||||
|
||||
private:
|
||||
void loadTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings();
|
||||
void saveTableViewColumnVisibilitySettings(int column);
|
||||
void loadTableViewColumnSortSettings();
|
||||
void saveTableViewColumnSortSettings(const int sort_column, const Qt::SortOrder sort_order);
|
||||
void loadTableHeaderState();
|
||||
void applyTableHeaderDefaults();
|
||||
void resetTableHeaderToDefault();
|
||||
void saveSortSettings(int column, Qt::SortOrder sort_order);
|
||||
void listZoom(float delta);
|
||||
void updateToolbar();
|
||||
|
||||
@@ -122,4 +123,6 @@ private:
|
||||
GameListRefreshThread* m_refresh_thread = nullptr;
|
||||
|
||||
QMovie* m_background_movie = nullptr;
|
||||
QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit;
|
||||
float m_background_opacity = 100.0f;
|
||||
};
|
||||
|
||||
@@ -86,16 +86,6 @@ const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All
|
||||
|
||||
MainWindow* g_main_window = nullptr;
|
||||
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
static const bool s_use_central_widget = false;
|
||||
#else
|
||||
// Qt Wayland is broken. Any sort of stacked widget usage fails to update,
|
||||
// leading to broken window resizes, no display rendering, etc. So, we mess
|
||||
// with the central widget instead. Which we can't do on xorg, because it
|
||||
// breaks window resizing there...
|
||||
static bool s_use_central_widget = false;
|
||||
#endif
|
||||
|
||||
// UI thread VM validity.
|
||||
static bool s_vm_valid = false;
|
||||
static bool s_vm_paused = false;
|
||||
@@ -113,19 +103,6 @@ MainWindow::MainWindow()
|
||||
{
|
||||
pxAssert(!g_main_window);
|
||||
g_main_window = this;
|
||||
|
||||
// Native window rendering is broken in wayland.
|
||||
// Let's work around it by disabling it for every widget besides
|
||||
// DisplayWidget.
|
||||
// Additionally, alien widget rendering is much more performant, so we
|
||||
// should have a nice responsiveness boost in our UI :)
|
||||
// QTBUG-133919, reported upstream by govanify
|
||||
QGuiApplication::setAttribute(Qt::AA_NativeWindows, false);
|
||||
QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
s_use_central_widget = DisplayContainer::isRunningOnWayland();
|
||||
#endif
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@@ -192,7 +169,7 @@ static void makeIconsMasks(QWidget* menu)
|
||||
|
||||
QWidget* MainWindow::getContentParent()
|
||||
{
|
||||
return s_use_central_widget ? static_cast<QWidget*>(this) : static_cast<QWidget*>(m_ui.mainContainer);
|
||||
return static_cast<QWidget*>(m_ui.mainContainer);
|
||||
}
|
||||
|
||||
void MainWindow::setupAdditionalUi()
|
||||
@@ -219,15 +196,7 @@ void MainWindow::setupAdditionalUi()
|
||||
m_game_list_widget = new GameListWidget(getContentParent());
|
||||
m_game_list_widget->initialize();
|
||||
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
|
||||
if (s_use_central_widget)
|
||||
{
|
||||
m_ui.mainContainer = nullptr; // setCentralWidget() will delete this
|
||||
setCentralWidget(m_game_list_widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.mainContainer->addWidget(m_game_list_widget);
|
||||
}
|
||||
m_ui.mainContainer->addWidget(m_game_list_widget);
|
||||
|
||||
m_status_progress_widget = new QProgressBar(m_ui.statusBar);
|
||||
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
@@ -357,8 +326,8 @@ void MainWindow::connectSignals()
|
||||
[this]() { doControllerSettings(ControllerSettingsWindow::Category::HotkeySettings); });
|
||||
connect(m_ui.actionAddGameDirectory, &QAction::triggered,
|
||||
[this]() { getSettingsWindow()->getGameListSettingsWidget()->addSearchDirectory(this); });
|
||||
connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false); });
|
||||
connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true); });
|
||||
connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false, true); });
|
||||
connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true, true); });
|
||||
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
|
||||
connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled);
|
||||
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
|
||||
@@ -532,7 +501,7 @@ void MainWindow::recreate()
|
||||
if (was_display_created)
|
||||
{
|
||||
g_emu_thread->setSurfaceless(true);
|
||||
while (m_display_widget || !g_emu_thread->isSurfaceless())
|
||||
while (m_display_surface || !g_emu_thread->isSurfaceless())
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
|
||||
m_display_created = false;
|
||||
@@ -547,7 +516,7 @@ void MainWindow::recreate()
|
||||
MainWindow* new_main_window = new MainWindow();
|
||||
pxAssert(g_main_window == new_main_window);
|
||||
new_main_window->initialize();
|
||||
new_main_window->refreshGameList(false);
|
||||
new_main_window->refreshGameList(false, false);
|
||||
new_main_window->show();
|
||||
deleteLater();
|
||||
|
||||
@@ -1033,11 +1002,10 @@ void MainWindow::updateWindowTitle()
|
||||
if (windowTitle() != main_title)
|
||||
setWindowTitle(main_title);
|
||||
|
||||
if (m_display_widget && !isRenderingToMain())
|
||||
if (m_display_container && !isRenderingToMain())
|
||||
{
|
||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
if (container->windowTitle() != display_title)
|
||||
container->setWindowTitle(display_title);
|
||||
if (m_display_container->windowTitle() != display_title)
|
||||
m_display_container->setWindowTitle(display_title);
|
||||
}
|
||||
|
||||
if (g_log_window)
|
||||
@@ -1052,7 +1020,7 @@ void MainWindow::updateWindowState(bool force_visible)
|
||||
|
||||
const bool hide_window = !isRenderingToMain() && shouldHideMainWindow();
|
||||
const bool disable_resize = Host::GetBoolSettingValue("UI", "DisableWindowResize", false);
|
||||
const bool has_window = s_vm_valid || m_display_widget;
|
||||
const bool has_window = s_vm_valid || m_display_surface;
|
||||
|
||||
// Need to test both valid and display widget because of startup (vm invalid while window is created).
|
||||
const bool visible = force_visible || !hide_window || !has_window;
|
||||
@@ -1065,8 +1033,8 @@ void MainWindow::updateWindowState(bool force_visible)
|
||||
QtUtils::SetWindowResizeable(this, resizeable);
|
||||
|
||||
// Update the display widget too if rendering separately.
|
||||
if (m_display_widget && !isRenderingToMain())
|
||||
QtUtils::SetWindowResizeable(getDisplayContainer(), resizeable);
|
||||
if (m_display_surface && !isRenderingToMain())
|
||||
QtUtils::SetWindowResizeable(m_display_container, resizeable);
|
||||
}
|
||||
|
||||
void MainWindow::setProgressBar(int current, int total)
|
||||
@@ -1093,26 +1061,20 @@ void MainWindow::clearProgressBar()
|
||||
|
||||
bool MainWindow::isShowingGameList() const
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
return (centralWidget() == m_game_list_widget);
|
||||
else
|
||||
return (m_ui.mainContainer->currentIndex() == 0);
|
||||
return (m_ui.mainContainer->currentIndex() == 0);
|
||||
}
|
||||
|
||||
bool MainWindow::isRenderingFullscreen() const
|
||||
{
|
||||
if (!MTGS::IsOpen() || !m_display_widget)
|
||||
if (!MTGS::IsOpen() || !m_display_surface)
|
||||
return false;
|
||||
|
||||
return getDisplayContainer()->isFullScreen();
|
||||
return m_display_container->isFullScreen();
|
||||
}
|
||||
|
||||
bool MainWindow::isRenderingToMain() const
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
return (m_display_widget && centralWidget() == m_display_widget);
|
||||
else
|
||||
return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) == 1);
|
||||
return (m_display_surface && m_ui.mainContainer->indexOf(m_display_container) == 1);
|
||||
}
|
||||
|
||||
bool MainWindow::shouldHideMouseCursor() const
|
||||
@@ -1137,16 +1099,16 @@ bool MainWindow::shouldMouseLock() const
|
||||
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
|
||||
return false;
|
||||
|
||||
if(m_display_created == false || m_display_widget == nullptr && !isRenderingToMain())
|
||||
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());
|
||||
|
||||
auto* displayWindow = isRenderingToMain() ? window() : m_display_widget->window();
|
||||
auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window();
|
||||
|
||||
if(displayWindow == nullptr)
|
||||
if (displayWindow == nullptr)
|
||||
return false;
|
||||
|
||||
return windowsHidden && (displayWindow->isActiveWindow() || displayWindow->isFullScreen());
|
||||
@@ -1194,7 +1156,7 @@ void MainWindow::switchToGameListView()
|
||||
|
||||
// switch to surfaceless. we have to wait until the display widget is gone before we swap over.
|
||||
g_emu_thread->setSurfaceless(true);
|
||||
while (m_display_widget)
|
||||
while (m_display_surface)
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
|
||||
}
|
||||
}
|
||||
@@ -1211,17 +1173,13 @@ void MainWindow::switchToEmulationView()
|
||||
if (s_vm_paused && !m_was_paused_on_surface_loss)
|
||||
g_emu_thread->setVMPaused(false);
|
||||
|
||||
if (m_display_widget)
|
||||
m_display_widget->setFocus();
|
||||
if (m_display_surface)
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::refreshGameList(bool invalidate_cache)
|
||||
void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error)
|
||||
{
|
||||
// can't do this while the VM is running because of CDVD
|
||||
if (s_vm_valid)
|
||||
return;
|
||||
|
||||
m_game_list_widget->refresh(invalidate_cache);
|
||||
m_game_list_widget->refresh(invalidate_cache, popup_on_error);
|
||||
}
|
||||
|
||||
void MainWindow::cancelGameListRefresh()
|
||||
@@ -1353,7 +1311,7 @@ void MainWindow::requestExit(bool allow_confirm)
|
||||
|
||||
void MainWindow::checkForSettingChanges()
|
||||
{
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
|
||||
updateWindowState();
|
||||
@@ -1363,10 +1321,10 @@ void MainWindow::checkForSettingChanges()
|
||||
|
||||
std::optional<WindowInfo> MainWindow::getWindowInfo()
|
||||
{
|
||||
if (!m_display_widget || isRenderingToMain())
|
||||
return QtUtils::GetWindowInfoForWidget(this);
|
||||
else if (QWidget* widget = getDisplayContainer())
|
||||
return QtUtils::GetWindowInfoForWidget(widget);
|
||||
if (!m_display_surface || isRenderingToMain())
|
||||
return QtUtils::GetWindowInfoForWindow(this);
|
||||
else if (m_display_surface)
|
||||
return QtUtils::GetWindowInfoForWindow(m_display_surface);
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1385,9 +1343,8 @@ void MainWindow::onGameListRefreshComplete()
|
||||
|
||||
void MainWindow::onGameListSelectionChanged()
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
m_ui.statusBar->showMessage(QString::fromStdString(entry->path));
|
||||
@@ -1395,9 +1352,8 @@ void MainWindow::onGameListSelectionChanged()
|
||||
|
||||
void MainWindow::onGameListEntryActivated()
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
if (s_vm_valid)
|
||||
@@ -1425,24 +1381,23 @@ void MainWindow::onGameListEntryActivated()
|
||||
}
|
||||
|
||||
// only resume if the option is enabled, and we have one for this game
|
||||
startGameListEntry(entry, resume.value() ? std::optional<s32>(-1) : std::optional<s32>(), std::nullopt);
|
||||
startGameListEntry(*entry, resume.value() ? std::optional<s32>(-1) : std::optional<s32>(), std::nullopt);
|
||||
}
|
||||
|
||||
void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
|
||||
QMenu menu;
|
||||
|
||||
if (entry)
|
||||
if (entry.has_value())
|
||||
{
|
||||
QAction* action = menu.addAction(tr("Properties..."));
|
||||
action->setEnabled(!entry->serial.empty() || entry->type == GameList::EntryType::ELF);
|
||||
if (action->isEnabled())
|
||||
{
|
||||
connect(action, &QAction::triggered, [entry]() {
|
||||
SettingsWindow::openGamePropertiesDialog(entry,
|
||||
SettingsWindow::openGamePropertiesDialog(&*entry,
|
||||
entry->title, entry->serial, entry->crc, entry->type == GameList::EntryType::ELF, nullptr);
|
||||
});
|
||||
}
|
||||
@@ -1454,7 +1409,7 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
});
|
||||
|
||||
action = menu.addAction(tr("Set Cover Image..."));
|
||||
connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(*entry); });
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
connect(menu.addAction(tr("Create Game Shortcut...")), &QAction::triggered, [this]() { MainWindow::onCreateGameShortcutTriggered(); });
|
||||
@@ -1466,38 +1421,37 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
|
||||
const time_t entry_played_time = GameList::GetCachedPlayedTimeForSerial(entry->serial);
|
||||
// Best two options given zero play time are to grey this out or to not show it at all.
|
||||
if (entry_played_time)
|
||||
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]()
|
||||
{ clearGameListEntryPlayTime(entry, entry_played_time); });
|
||||
connect(menu.addAction(tr("Reset Play Time")), &QAction::triggered, [this, entry, entry_played_time]() { clearGameListEntryPlayTime(*entry, entry_played_time); });
|
||||
|
||||
// Check Wiki Page functionality is based on a serial redirect.
|
||||
if (!entry->serial.empty())
|
||||
connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(entry); });
|
||||
connect(menu.addAction(tr("Check Wiki Page")), &QAction::triggered, [this, entry]() { goToWikiPage(*entry); });
|
||||
|
||||
action = menu.addAction(tr("Open Snapshots Folder"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { openSnapshotsFolderForGame(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { openSnapshotsFolderForGame(*entry); });
|
||||
menu.addSeparator();
|
||||
|
||||
if (!s_vm_valid)
|
||||
{
|
||||
action = menu.addAction(tr("Default Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry); });
|
||||
|
||||
// Make bold to indicate it's the default choice when double-clicking
|
||||
if (!VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1))
|
||||
QtUtils::MarkActionAsDefault(action);
|
||||
|
||||
action = menu.addAction(tr("Fast Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, true); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry, std::nullopt, true); });
|
||||
|
||||
action = menu.addAction(tr("Full Boot"));
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, false); });
|
||||
connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(*entry, std::nullopt, false); });
|
||||
|
||||
if (m_ui.menuDebug->menuAction()->isVisible())
|
||||
{
|
||||
action = menu.addAction(tr("Boot and Debug"));
|
||||
connect(action, &QAction::triggered, [this, entry]() {
|
||||
DebugInterface::setPauseOnEntry(true);
|
||||
startGameListEntry(entry);
|
||||
startGameListEntry(*entry);
|
||||
DebuggerWindow::getInstance()->show();
|
||||
});
|
||||
}
|
||||
@@ -1603,7 +1557,7 @@ void MainWindow::onSaveStateMenuAboutToShow()
|
||||
|
||||
void MainWindow::onStartFullscreenUITriggered()
|
||||
{
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
g_emu_thread->stopFullscreenUI();
|
||||
else
|
||||
g_emu_thread->startFullscreenUI(Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
|
||||
@@ -1769,7 +1723,10 @@ void MainWindow::onToolsCoverDownloaderTriggered()
|
||||
#if !defined(__APPLE__)
|
||||
void MainWindow::onCreateGameShortcutTriggered()
|
||||
{
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
const QString title = QString::fromStdString(entry->GetTitle());
|
||||
const QString path = QString::fromStdString(entry->path);
|
||||
VMLock lock(pauseAndLockVM());
|
||||
@@ -2038,7 +1995,7 @@ void MainWindow::onVMPaused()
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_last_fps_status = m_status_verbose_widget->text();
|
||||
m_status_verbose_widget->setText(tr("Paused"));
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
}
|
||||
|
||||
@@ -2060,10 +2017,10 @@ void MainWindow::onVMResumed()
|
||||
updateStatusBarWidgetVisibility();
|
||||
m_status_verbose_widget->setText(m_last_fps_status);
|
||||
m_last_fps_status = QString();
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
{
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2095,14 +2052,14 @@ void MainWindow::onVMStopped()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_display_widget)
|
||||
if (m_display_surface)
|
||||
updateDisplayWidgetCursor();
|
||||
else
|
||||
switchToGameListView();
|
||||
|
||||
// reload played time
|
||||
if (m_game_list_widget->isShowingGameList())
|
||||
m_game_list_widget->refresh(false);
|
||||
m_game_list_widget->refresh(false, false);
|
||||
}
|
||||
|
||||
void MainWindow::onGameChanged(const QString& title, const QString& elf_override, const QString& disc_path,
|
||||
@@ -2365,22 +2322,17 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
DevCon.WriteLn("acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false",
|
||||
fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false");
|
||||
|
||||
QWidget* container = m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
|
||||
const bool is_fullscreen = isRenderingFullscreen();
|
||||
const bool is_rendering_to_main = isRenderingToMain();
|
||||
const bool changing_surfaceless = (!m_display_widget != surfaceless);
|
||||
const bool changing_surfaceless = (!m_display_surface != surfaceless);
|
||||
if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main &&
|
||||
!changing_surfaceless)
|
||||
{
|
||||
return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo();
|
||||
return m_display_surface ? m_display_surface->getWindowInfo() : WindowInfo();
|
||||
}
|
||||
|
||||
// Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
|
||||
// .. except on Wayland, where everything tends to break if you don't recreate.
|
||||
const bool has_container = (m_display_container != nullptr);
|
||||
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container &&
|
||||
!needs_container && !changing_surfaceless)
|
||||
if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && !changing_surfaceless)
|
||||
{
|
||||
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
|
||||
|
||||
@@ -2390,23 +2342,23 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
container->showFullScreen();
|
||||
m_display_container->showFullScreen();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
|
||||
container->setGeometry(geometry());
|
||||
m_display_container->setGeometry(geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
container->showNormal();
|
||||
m_display_container->showNormal();
|
||||
}
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
m_display_container->setFocus();
|
||||
updateWindowState();
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
return m_display_widget->getWindowInfo();
|
||||
return m_display_surface->getWindowInfo();
|
||||
}
|
||||
|
||||
destroyDisplayWidget(surfaceless);
|
||||
@@ -2425,7 +2377,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
|
||||
createDisplayWidget(fullscreen, render_to_main);
|
||||
|
||||
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
|
||||
std::optional<WindowInfo> wi = m_display_surface->getWindowInfo();
|
||||
if (!wi.has_value())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
|
||||
@@ -2433,14 +2385,13 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
g_emu_thread->connectDisplaySignals(m_display_widget);
|
||||
g_emu_thread->connectDisplaySignals(m_display_surface);
|
||||
|
||||
updateWindowTitle();
|
||||
updateWindowState();
|
||||
|
||||
updateDisplayWidgetCursor();
|
||||
m_display_widget->setFocus();
|
||||
|
||||
m_display_container->setFocus();
|
||||
return wi;
|
||||
}
|
||||
|
||||
@@ -2454,64 +2405,46 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
|
||||
QGuiApplication::sync();
|
||||
}
|
||||
|
||||
QWidget* container;
|
||||
if (DisplayContainer::isNeeded(fullscreen, render_to_main))
|
||||
m_display_surface = new DisplaySurface();
|
||||
if (fullscreen || !render_to_main)
|
||||
{
|
||||
m_display_container = new DisplayContainer();
|
||||
m_display_widget = new DisplayWidget(m_display_container);
|
||||
m_display_container->setDisplayWidget(m_display_widget);
|
||||
container = m_display_container;
|
||||
m_display_container = m_display_surface->createWindowContainer();
|
||||
m_display_container->setWindowTitle(windowTitle());
|
||||
m_display_container->setWindowIcon(windowIcon());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? getContentParent() : nullptr);
|
||||
container = m_display_widget;
|
||||
}
|
||||
|
||||
if (fullscreen || !render_to_main)
|
||||
{
|
||||
container->setWindowTitle(windowTitle());
|
||||
container->setWindowIcon(windowIcon());
|
||||
m_display_container = m_display_surface->createWindowContainer(getContentParent());
|
||||
}
|
||||
|
||||
if (fullscreen)
|
||||
{
|
||||
// Don't risk doing this on Wayland, it really doesn't like window state changes,
|
||||
// and positioning has no effect anyway.
|
||||
if (!s_use_central_widget)
|
||||
{
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
container->move(pos());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
}
|
||||
|
||||
container->showFullScreen();
|
||||
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
m_display_container->move(pos());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
m_display_container->showFullScreen();
|
||||
}
|
||||
else if (!render_to_main)
|
||||
{
|
||||
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
|
||||
container->setGeometry(geometry());
|
||||
m_display_container->setGeometry(geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
container->showNormal();
|
||||
}
|
||||
else if (s_use_central_widget)
|
||||
{
|
||||
m_game_list_widget->setVisible(false);
|
||||
takeCentralWidget();
|
||||
m_game_list_widget->setParent(this); // takeCentralWidget() removes parent
|
||||
setCentralWidget(m_display_widget);
|
||||
m_display_widget->setFocus();
|
||||
update();
|
||||
m_display_container->showNormal();
|
||||
}
|
||||
else
|
||||
{
|
||||
pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget");
|
||||
m_ui.mainContainer->addWidget(container);
|
||||
m_ui.mainContainer->addWidget(m_display_container);
|
||||
m_ui.mainContainer->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
// Attatch drag and drop signals
|
||||
connect(m_display_surface, &DisplaySurface::dragEnterEvent, this, &MainWindow::dragEnterEvent);
|
||||
connect(m_display_surface, &DisplaySurface::dropEvent, this, &MainWindow::dropEvent);
|
||||
|
||||
updateDisplayRelatedActions(true, render_to_main, fullscreen);
|
||||
|
||||
// We need the surface visible.
|
||||
@@ -2520,7 +2453,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
|
||||
|
||||
void MainWindow::displayResizeRequested(qint32 width, qint32 height)
|
||||
{
|
||||
if (!m_display_widget)
|
||||
if (!m_display_surface)
|
||||
return;
|
||||
|
||||
// unapply the pixel scaling factor for hidpi
|
||||
@@ -2528,15 +2461,15 @@ 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));
|
||||
|
||||
if (m_display_container || !m_display_widget->parent())
|
||||
if (!m_display_container->parent())
|
||||
{
|
||||
// no parent - rendering to separate window. easy.
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(getDisplayContainer(), width, height);
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_container, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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_widget->height();
|
||||
const s32 extra_height = this->height() - m_display_container->height();
|
||||
QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height);
|
||||
}
|
||||
|
||||
@@ -2547,7 +2480,7 @@ void MainWindow::mouseModeRequested(bool relative_mode, bool hide_cursor)
|
||||
|
||||
m_relative_mouse_mode = relative_mode;
|
||||
m_hide_mouse_cursor = hide_cursor;
|
||||
if (m_display_widget && !s_vm_paused)
|
||||
if (m_display_surface && !s_vm_paused)
|
||||
updateDisplayWidgetCursor();
|
||||
}
|
||||
|
||||
@@ -2563,73 +2496,46 @@ void MainWindow::releaseRenderWindow()
|
||||
|
||||
void MainWindow::destroyDisplayWidget(bool show_game_list)
|
||||
{
|
||||
if (!m_display_widget)
|
||||
if (!m_display_surface)
|
||||
return;
|
||||
|
||||
if (!isRenderingFullscreen() && !isRenderingToMain())
|
||||
saveDisplayWindowGeometryToConfig();
|
||||
|
||||
if (m_display_container)
|
||||
m_display_container->removeDisplayWidget();
|
||||
|
||||
if (isRenderingToMain())
|
||||
{
|
||||
if (s_use_central_widget)
|
||||
pxAssertRel(m_ui.mainContainer->indexOf(m_display_container) == 1, "Display widget in stack");
|
||||
m_ui.mainContainer->removeWidget(m_display_container);
|
||||
if (show_game_list)
|
||||
{
|
||||
pxAssertRel(centralWidget() == m_display_widget, "Display widget is currently central");
|
||||
takeCentralWidget();
|
||||
if (show_game_list)
|
||||
{
|
||||
m_game_list_widget->setVisible(true);
|
||||
setCentralWidget(m_game_list_widget);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pxAssertRel(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack");
|
||||
m_ui.mainContainer->removeWidget(m_display_widget);
|
||||
if (show_game_list)
|
||||
{
|
||||
m_ui.mainContainer->setCurrentIndex(0);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
m_ui.mainContainer->setCurrentIndex(0);
|
||||
m_game_list_widget->resizeTableViewColumnsToFit();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_display_widget)
|
||||
{
|
||||
m_display_widget->destroy();
|
||||
m_display_widget = nullptr;
|
||||
}
|
||||
|
||||
if (m_display_container)
|
||||
{
|
||||
m_display_container->deleteLater();
|
||||
m_display_container = nullptr;
|
||||
}
|
||||
// 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;
|
||||
|
||||
updateDisplayRelatedActions(false, false, false);
|
||||
}
|
||||
|
||||
void MainWindow::updateDisplayWidgetCursor()
|
||||
{
|
||||
pxAssertRel(m_display_widget, "Should have a display widget");
|
||||
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode);
|
||||
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
|
||||
pxAssertRel(m_display_surface, "Should have a display widget");
|
||||
m_display_surface->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode);
|
||||
m_display_surface->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
|
||||
}
|
||||
|
||||
void MainWindow::focusDisplayWidget()
|
||||
{
|
||||
if (!m_display_widget || centralWidget() != m_display_widget)
|
||||
if (!m_display_surface || centralWidget() != m_display_container)
|
||||
return;
|
||||
|
||||
m_display_widget->setFocus();
|
||||
}
|
||||
|
||||
QWidget* MainWindow::getDisplayContainer() const
|
||||
{
|
||||
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
|
||||
m_display_container->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::setupMouseMoveHandler()
|
||||
@@ -2658,18 +2564,29 @@ void MainWindow::checkMousePosition(int x, int y)
|
||||
// physical mouse position
|
||||
const QPoint physicalPos(x, y);
|
||||
|
||||
const auto* displayWindow = getDisplayContainer()->window();
|
||||
const auto* displayWindow = m_display_surface;
|
||||
|
||||
// logical (DIP) frame rect
|
||||
QRectF logicalBounds = displayWindow->geometry();
|
||||
const QSize logicalSize = displayWindow->size();
|
||||
const QPoint logicalPosition = displayWindow->position() + displayWindow->parent()->position();
|
||||
|
||||
// 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
|
||||
const QScreen* screen = displayWindow->screen();
|
||||
|
||||
// If we fail to get the screen associated with the window, avoid mouse locking as it's probably in an unexpected position.
|
||||
if (!screen)
|
||||
return;
|
||||
|
||||
const QPoint screenPosition = screen->geometry().topLeft();
|
||||
|
||||
// physical frame rect
|
||||
const qreal scale = displayWindow->devicePixelRatioF();
|
||||
QRectF physicalBounds(
|
||||
logicalBounds.x() * scale,
|
||||
logicalBounds.y() * scale,
|
||||
logicalBounds.width() * scale,
|
||||
logicalBounds.height() * scale);
|
||||
const qreal scale = displayWindow->devicePixelRatio();
|
||||
const QRectF physicalBounds(
|
||||
screenPosition.x() + (logicalPosition.x() - screenPosition.x()) * scale,
|
||||
screenPosition.y() + (logicalPosition.y() - screenPosition.y()) * scale,
|
||||
logicalSize.width() * scale,
|
||||
logicalSize.height() * scale);
|
||||
|
||||
if (physicalBounds.contains(physicalPos))
|
||||
return;
|
||||
@@ -2682,14 +2599,13 @@ void MainWindow::checkMousePosition(int x, int y)
|
||||
|
||||
void MainWindow::saveDisplayWindowGeometryToConfig()
|
||||
{
|
||||
QWidget* container = getDisplayContainer();
|
||||
if (container->windowState() & Qt::WindowFullScreen)
|
||||
if (m_display_container->windowState() & Qt::WindowFullScreen)
|
||||
{
|
||||
// if we somehow ended up here, don't save the fullscreen state to the config
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray geometry = getDisplayContainer()->saveGeometry();
|
||||
const QByteArray geometry = m_display_container->saveGeometry();
|
||||
const QByteArray geometry_b64 = geometry.toBase64();
|
||||
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
|
||||
if (old_geometry_b64 != geometry_b64.constData())
|
||||
@@ -2703,18 +2619,17 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
|
||||
{
|
||||
const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
|
||||
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
|
||||
QWidget* container = getDisplayContainer();
|
||||
if (!geometry.isEmpty())
|
||||
{
|
||||
container->restoreGeometry(geometry);
|
||||
m_display_container->restoreGeometry(geometry);
|
||||
|
||||
// make sure we're not loading a dodgy config which had fullscreen set...
|
||||
container->setWindowState(container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
|
||||
m_display_container->setWindowState(m_display_container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
|
||||
}
|
||||
else
|
||||
{
|
||||
// default size
|
||||
container->resize(640, 480);
|
||||
m_display_container->resize(640, 480);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2725,7 +2640,7 @@ SettingsWindow* MainWindow::getSettingsWindow()
|
||||
m_settings_window = new SettingsWindow();
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this, &MainWindow::onThemeChanged);
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::languageChanged, this, &MainWindow::onLanguageChanged);
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::backgroundChanged, m_game_list_widget, [this] { m_game_list_widget->setCustomBackground(true); });
|
||||
connect(m_settings_window->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::backgroundChanged, m_game_list_widget, [this] { m_game_list_widget->setCustomBackground(); });
|
||||
connect(m_settings_window->getGameListSettingsWidget(), &GameListSettingsWidget::preferEnglishGameListChanged, this, [] {
|
||||
g_main_window->m_game_list_widget->refreshGridCovers();
|
||||
});
|
||||
@@ -2760,13 +2675,21 @@ void MainWindow::doGameSettings(const char* category)
|
||||
// prefer to use a game list entry, if we have one, that way the summary is populated
|
||||
if (!s_current_disc_path.isEmpty() || !s_current_elf_override.isEmpty())
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const QString& path = (s_current_elf_override.isEmpty() ? s_current_disc_path : s_current_elf_override);
|
||||
const GameList::Entry* entry = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry)
|
||||
|
||||
std::optional<GameList::Entry> entry;
|
||||
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry_ptr = GameList::GetEntryForPath(path.toUtf8().constData());
|
||||
if (entry_ptr)
|
||||
entry = *entry_ptr;
|
||||
}
|
||||
|
||||
if (entry.has_value())
|
||||
{
|
||||
SettingsWindow::openGamePropertiesDialog(
|
||||
entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
|
||||
&*entry, entry->title, entry->serial, entry->crc, !s_current_elf_override.isEmpty(), category);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2856,16 +2779,16 @@ QString MainWindow::getDiscDevicePath(const QString& title)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot, bool load_backup)
|
||||
void MainWindow::startGameListEntry(const GameList::Entry& entry, std::optional<s32> save_slot, std::optional<bool> fast_boot, bool load_backup)
|
||||
{
|
||||
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
|
||||
params->fast_boot = fast_boot;
|
||||
|
||||
GameList::FillBootParametersForEntry(params.get(), entry);
|
||||
GameList::FillBootParametersForEntry(params.get(), &entry);
|
||||
|
||||
if (save_slot.has_value() && !entry->serial.empty())
|
||||
if (save_slot.has_value() && !entry.serial.empty())
|
||||
{
|
||||
std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value(), load_backup);
|
||||
std::string state_filename = VMManager::GetSaveStateFileName(entry.serial.c_str(), entry.crc, save_slot.value(), load_backup);
|
||||
if (!FileSystem::FileExists(state_filename.c_str()))
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("This save state does not exist."));
|
||||
@@ -2878,15 +2801,15 @@ void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional<
|
||||
g_emu_thread->startVM(std::move(params));
|
||||
}
|
||||
|
||||
void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
|
||||
void MainWindow::setGameListEntryCoverImage(const GameList::Entry& entry)
|
||||
{
|
||||
const QString filename = QDir::toNativeSeparators(
|
||||
QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(), tr("All Cover Image Types (*.jpg *.jpeg *.png *.webp)")));
|
||||
if (filename.isEmpty())
|
||||
return;
|
||||
|
||||
const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(entry));
|
||||
const QString new_filename = QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData(), true));
|
||||
const QString old_filename = QString::fromStdString(GameList::GetCoverImagePathForEntry(&entry));
|
||||
const QString new_filename = QString::fromStdString(GameList::GetNewCoverImagePathForEntry(&entry, filename.toUtf8().constData(), true));
|
||||
if (new_filename.isEmpty())
|
||||
return;
|
||||
|
||||
@@ -2927,31 +2850,31 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
|
||||
m_game_list_widget->refreshGridCovers();
|
||||
}
|
||||
|
||||
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry, const time_t entry_played_time)
|
||||
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry& entry, const time_t entry_played_time)
|
||||
{
|
||||
if (QMessageBox::question(this, tr("Confirm Reset"),
|
||||
tr("Are you sure you want to reset the play time for '%1' (%2)?\n\nYour current play time is %3.\n\nThis action cannot be undone.")
|
||||
.arg(entry->title.empty() ? tr("empty title") : QString::fromStdString(entry->title),
|
||||
entry->serial.empty() ? tr("no serial") : QString::fromStdString(entry->serial),
|
||||
QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))),
|
||||
(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes)
|
||||
.arg(entry.title.empty() ? tr("empty title") : QString::fromStdString(entry.title),
|
||||
entry.serial.empty() ? tr("no serial") : QString::fromStdString(entry.serial),
|
||||
QString::fromStdString(GameList::FormatTimespan(entry_played_time, true))),
|
||||
(QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes)
|
||||
{
|
||||
GameList::ClearPlayedTimeForSerial(entry->serial);
|
||||
m_game_list_widget->refresh(false);
|
||||
GameList::ClearPlayedTimeForSerial(entry.serial);
|
||||
m_game_list_widget->refresh(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::goToWikiPage(const GameList::Entry* entry)
|
||||
void MainWindow::goToWikiPage(const GameList::Entry& entry)
|
||||
{
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry->serial).c_str());
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry.serial).c_str());
|
||||
}
|
||||
|
||||
void MainWindow::openSnapshotsFolderForGame(const GameList::Entry* entry)
|
||||
void MainWindow::openSnapshotsFolderForGame(const GameList::Entry& entry)
|
||||
{
|
||||
// Go to top-level snapshots directory if not organizing by game.
|
||||
if (EmuConfig.GS.OrganizeSnapshotsByGame && entry && !entry->title.empty())
|
||||
if (EmuConfig.GS.OrganizeSnapshotsByGame && !entry.title.empty())
|
||||
{
|
||||
std::string game_name = entry->title;
|
||||
std::string game_name = entry.title;
|
||||
Path::SanitizeFileName(&game_name);
|
||||
|
||||
const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name);
|
||||
@@ -3026,11 +2949,11 @@ void MainWindow::loadSaveStateSlot(s32 slot, bool load_backup)
|
||||
else
|
||||
{
|
||||
// we're not currently running, therefore we must've right clicked in the game list
|
||||
const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry)
|
||||
const std::optional<GameList::Entry> entry = m_game_list_widget->getSelectedEntry();
|
||||
if (!entry.has_value())
|
||||
return;
|
||||
|
||||
startGameListEntry(entry, slot, std::nullopt, load_backup);
|
||||
startGameListEntry(*entry, slot, std::nullopt, load_backup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3256,7 +3179,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||
// period when there's no window, and Qt might shut us down. So avoid it there.
|
||||
// On MacOS, it forces a workspace switch, which is kinda jarring.
|
||||
#ifndef __APPLE__
|
||||
const bool was_fullscreen = g_emu_thread->isFullscreen() && !s_use_central_widget;
|
||||
const bool was_fullscreen = g_emu_thread->isFullscreen();
|
||||
#else
|
||||
const bool was_fullscreen = false;
|
||||
#endif
|
||||
@@ -3275,25 +3198,20 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
|
||||
|
||||
g_emu_thread->setFullscreen(false, false);
|
||||
|
||||
// Container could change... thanks Wayland.
|
||||
QWidget* container;
|
||||
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() ||
|
||||
!(container = getDisplayContainer()) || container->isFullScreen()))
|
||||
// Process events untill both EmuThread and Qt have finished exiting fullscreen
|
||||
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_container->isFullScreen()))
|
||||
{
|
||||
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen).
|
||||
QWidget* dialog_parent = getDisplayContainer();
|
||||
|
||||
// Ensure main window is visible.
|
||||
if (!g_main_window->isVisible())
|
||||
g_main_window->show();
|
||||
g_main_window->raise();
|
||||
g_main_window->activateWindow();
|
||||
|
||||
return VMLock(dialog_parent, was_paused, was_fullscreen);
|
||||
return VMLock(m_display_container, was_paused, was_fullscreen);
|
||||
}
|
||||
|
||||
void MainWindow::rescanFile(const std::string& path)
|
||||
@@ -3303,23 +3221,30 @@ void MainWindow::rescanFile(const std::string& path)
|
||||
|
||||
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
|
||||
: m_dialog_parent(dialog_parent)
|
||||
, m_has_lock(true)
|
||||
, m_was_paused(was_paused)
|
||||
, m_was_fullscreen(was_fullscreen)
|
||||
{
|
||||
QtHost::LockVMWithDialog();
|
||||
}
|
||||
|
||||
MainWindow::VMLock::VMLock(VMLock&& lock)
|
||||
: m_dialog_parent(lock.m_dialog_parent)
|
||||
, m_has_lock(lock.m_has_lock)
|
||||
, m_was_paused(lock.m_was_paused)
|
||||
, m_was_fullscreen(lock.m_was_fullscreen)
|
||||
{
|
||||
lock.m_dialog_parent = nullptr;
|
||||
lock.m_has_lock = false;
|
||||
lock.m_was_paused = true;
|
||||
lock.m_was_fullscreen = false;
|
||||
}
|
||||
|
||||
MainWindow::VMLock::~VMLock()
|
||||
{
|
||||
if (m_has_lock)
|
||||
QtHost::UnlockVMWithDialog();
|
||||
|
||||
if (m_was_fullscreen)
|
||||
{
|
||||
g_main_window->m_is_temporarily_windowed = false;
|
||||
@@ -3332,10 +3257,15 @@ MainWindow::VMLock::~VMLock()
|
||||
|
||||
MainWindow::VMLock& MainWindow::VMLock::operator=(VMLock&& lock)
|
||||
{
|
||||
if (m_has_lock)
|
||||
QtHost::UnlockVMWithDialog();
|
||||
|
||||
m_has_lock = lock.m_has_lock;
|
||||
m_dialog_parent = lock.m_dialog_parent;
|
||||
m_was_paused = lock.m_was_paused;
|
||||
m_was_fullscreen = lock.m_was_fullscreen;
|
||||
|
||||
lock.m_has_lock = false;
|
||||
lock.m_dialog_parent = nullptr;
|
||||
lock.m_was_paused = true;
|
||||
lock.m_was_fullscreen = false;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
class QProgressBar;
|
||||
|
||||
class AutoUpdaterDialog;
|
||||
class DisplayWidget;
|
||||
class DisplaySurface;
|
||||
class DisplayContainer;
|
||||
class GameListWidget;
|
||||
class ControllerSettingsWindow;
|
||||
@@ -69,6 +69,7 @@ public:
|
||||
friend MainWindow;
|
||||
|
||||
QWidget* m_dialog_parent;
|
||||
bool m_has_lock;
|
||||
bool m_was_paused;
|
||||
bool m_was_fullscreen;
|
||||
};
|
||||
@@ -113,7 +114,7 @@ public:
|
||||
void checkMousePosition(int x, int y);
|
||||
public Q_SLOTS:
|
||||
void checkForUpdates(bool display_message, bool force_check);
|
||||
void refreshGameList(bool invalidate_cache);
|
||||
void refreshGameList(bool invalidate_cache, bool popup_on_error);
|
||||
void cancelGameListRefresh();
|
||||
void reportInfo(const QString& title, const QString& message);
|
||||
void reportError(const QString& title, const QString& message);
|
||||
@@ -257,7 +258,6 @@ private:
|
||||
bool shouldAbortForMemcardBusy(const VMLock& lock);
|
||||
|
||||
QWidget* getContentParent();
|
||||
QWidget* getDisplayContainer() const;
|
||||
void saveDisplayWindowGeometryToConfig();
|
||||
void restoreDisplayWindowGeometryFromConfig();
|
||||
void createDisplayWidget(bool fullscreen, bool render_to_main);
|
||||
@@ -274,11 +274,11 @@ private:
|
||||
QString getDiscDevicePath(const QString& title);
|
||||
|
||||
void startGameListEntry(
|
||||
const GameList::Entry* entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt, bool load_backup = false);
|
||||
void setGameListEntryCoverImage(const GameList::Entry* entry);
|
||||
void clearGameListEntryPlayTime(const GameList::Entry* entry, const time_t entry_played_time);
|
||||
void goToWikiPage(const GameList::Entry* entry);
|
||||
void openSnapshotsFolderForGame(const GameList::Entry* entry);
|
||||
const GameList::Entry& entry, std::optional<s32> save_slot = std::nullopt, std::optional<bool> fast_boot = std::nullopt, bool load_backup = false);
|
||||
void setGameListEntryCoverImage(const GameList::Entry& entry);
|
||||
void clearGameListEntryPlayTime(const GameList::Entry& entry, const time_t entry_played_time);
|
||||
void goToWikiPage(const GameList::Entry& entry);
|
||||
void openSnapshotsFolderForGame(const GameList::Entry& entry);
|
||||
|
||||
std::optional<bool> promptForResumeState(const QString& save_state_path);
|
||||
void loadSaveStateSlot(s32 slot, bool load_backup = false);
|
||||
@@ -291,8 +291,8 @@ private:
|
||||
Ui::MainWindow m_ui;
|
||||
|
||||
GameListWidget* m_game_list_widget = nullptr;
|
||||
DisplayWidget* m_display_widget = nullptr;
|
||||
DisplayContainer* m_display_container = nullptr;
|
||||
DisplaySurface* m_display_surface = nullptr;
|
||||
QWidget* m_display_container = nullptr;
|
||||
|
||||
SettingsWindow* m_settings_window = nullptr;
|
||||
ControllerSettingsWindow* m_controller_settings_window = nullptr;
|
||||
|
||||
@@ -88,12 +88,13 @@ static QTimer* s_settings_save_timer = nullptr;
|
||||
static std::unique_ptr<INISettingsInterface> s_base_settings_interface;
|
||||
static bool s_batch_mode = false;
|
||||
static bool s_nogui_mode = false;
|
||||
static bool s_start_fullscreen_ui = false;
|
||||
static bool s_start_fullscreen_ui_fullscreen = false;
|
||||
static bool s_start_big_picture_mode = false;
|
||||
static bool s_start_fullscreen = false;
|
||||
static bool s_test_config_and_exit = false;
|
||||
static bool s_run_setup_wizard = false;
|
||||
static bool s_cleanup_after_update = false;
|
||||
static bool s_boot_and_debug = false;
|
||||
static std::atomic_int s_vm_locked_with_dialog = 0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// CPU Thread
|
||||
@@ -495,6 +496,11 @@ void EmuThread::setFullscreen(bool fullscreen, bool allow_render_to_main)
|
||||
return;
|
||||
}
|
||||
|
||||
// HACK: Prevent entering/exiting fullscreen mode when a dialog is shown, so
|
||||
// that we don't destroy the dialog while inside its exec function.
|
||||
if (s_vm_locked_with_dialog > 0)
|
||||
return;
|
||||
|
||||
if (!MTGS::IsOpen() || m_is_fullscreen == fullscreen)
|
||||
return;
|
||||
|
||||
@@ -764,12 +770,12 @@ void EmuThread::enumerateVibrationMotors()
|
||||
onVibrationMotorsEnumerated(qmotors);
|
||||
}
|
||||
|
||||
void EmuThread::connectDisplaySignals(DisplayWidget* widget)
|
||||
void EmuThread::connectDisplaySignals(DisplaySurface* widget)
|
||||
{
|
||||
widget->disconnect(this);
|
||||
|
||||
connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
|
||||
connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
|
||||
connect(widget, &DisplaySurface::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
|
||||
connect(widget, &DisplaySurface::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
|
||||
}
|
||||
|
||||
void EmuThread::onDisplayWindowResized(int width, int height, float scale)
|
||||
@@ -959,6 +965,9 @@ void Host::OnGameChanged(const std::string& title, const std::string& elf_overri
|
||||
|
||||
void EmuThread::updatePerformanceMetrics(bool force)
|
||||
{
|
||||
if (!g_main_window)
|
||||
return;
|
||||
|
||||
if (VMManager::HasValidVM())
|
||||
{
|
||||
QString gs_stat;
|
||||
@@ -1152,7 +1161,7 @@ void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directo
|
||||
if (!filters.empty())
|
||||
{
|
||||
filters_str.append(QStringLiteral("All File Types (%1)")
|
||||
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
|
||||
.arg(QString::fromStdString(StringUtil::JoinString(filters.begin(), filters.end(), " "))));
|
||||
for (const std::string& filter : filters)
|
||||
{
|
||||
filters_str.append(
|
||||
@@ -1713,6 +1722,16 @@ void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
|
||||
emit g_emu_thread->onMouseModeRequested(relative_mode, hide_cursor);
|
||||
}
|
||||
|
||||
void QtHost::LockVMWithDialog()
|
||||
{
|
||||
s_vm_locked_with_dialog++;
|
||||
}
|
||||
|
||||
void QtHost::UnlockVMWithDialog()
|
||||
{
|
||||
s_vm_locked_with_dialog--;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
class QtHostProgressCallback final : public BaseProgressCallback
|
||||
@@ -2168,7 +2187,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
else if (CHECK_ARG(QStringLiteral("-fullscreen")))
|
||||
{
|
||||
AutoBoot(autoboot)->fullscreen = true;
|
||||
s_start_fullscreen_ui_fullscreen = true;
|
||||
s_start_fullscreen = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-nofullscreen")))
|
||||
@@ -2183,7 +2202,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-bigpicture")))
|
||||
{
|
||||
s_start_fullscreen_ui = true;
|
||||
s_start_big_picture_mode = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-testconfig")))
|
||||
@@ -2244,7 +2263,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
|
||||
// if we don't have autoboot, we definitely don't want batch mode (because that'll skip
|
||||
// scanning the game list).
|
||||
if (s_batch_mode && !s_start_fullscreen_ui && !autoboot)
|
||||
if (s_batch_mode && !s_start_big_picture_mode && !autoboot)
|
||||
{
|
||||
QMessageBox::critical(nullptr, QStringLiteral("Error"),
|
||||
s_nogui_mode ? QStringLiteral("Cannot use no-gui mode, because no boot filename was specified.") :
|
||||
@@ -2385,7 +2404,7 @@ int main(int argc, char* argv[])
|
||||
|
||||
// When running in batch mode, ensure game list is loaded, but don't scan for any new files.
|
||||
if (!s_batch_mode)
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, false);
|
||||
else
|
||||
GameList::Refresh(false, true);
|
||||
|
||||
@@ -2398,8 +2417,9 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Initialize big picture mode if requested by command line or settings.
|
||||
if (s_start_fullscreen_ui || Host::GetBaseBoolSettingValue("UI", "StartBigPictureMode", false))
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen_ui_fullscreen);
|
||||
// As CLI arguments are baked-in, they're tracked separately from settings which can be changed during runtime.
|
||||
if (s_start_big_picture_mode || Host::GetBaseBoolSettingValue("UI", "StartBigPictureMode", false))
|
||||
g_emu_thread->startFullscreenUI(s_start_fullscreen || Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
|
||||
|
||||
if (s_boot_and_debug || DebuggerWindow::shouldShowOnStartup())
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
class SettingsInterface;
|
||||
|
||||
class DisplayWidget;
|
||||
class DisplaySurface;
|
||||
struct VMBootParameters;
|
||||
|
||||
enum class CDVD_SourceType : uint8_t;
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
|
||||
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
|
||||
std::optional<WindowInfo> acquireRenderWindow(bool recreate_window);
|
||||
void connectDisplaySignals(DisplayWidget* widget);
|
||||
void connectDisplaySignals(DisplaySurface* widget);
|
||||
void releaseRenderWindow();
|
||||
|
||||
void startBackgroundControllerPollTimer();
|
||||
@@ -290,4 +290,10 @@ namespace QtHost
|
||||
|
||||
/// Compare strings in the locale of the current UI language
|
||||
int LocaleSensitiveCompare(QStringView lhs, QStringView rhs);
|
||||
|
||||
/// Determines whether or not requests to enter/exit fullscreen mode should
|
||||
/// be ignored. This is a hack so that we don't destroy a dialog box while
|
||||
/// inside its exec function, which would cause a crash.
|
||||
void LockVMWithDialog();
|
||||
void UnlockVMWithDialog();
|
||||
} // namespace QtHost
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
#include <QtCore/QtGlobal>
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtGui/QAction>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QDesktopServices>
|
||||
#include <QtGui/QKeyEvent>
|
||||
#include <QtGui/QScreen>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
@@ -40,8 +39,6 @@
|
||||
#if defined(_WIN32)
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include <Shlobj.h>
|
||||
#elif !defined(APPLE)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
namespace QtUtils
|
||||
@@ -139,6 +136,126 @@ namespace QtUtils
|
||||
ResizeColumnsForView(view, widths);
|
||||
}
|
||||
|
||||
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity)
|
||||
{
|
||||
if (!pm || pm->isNull() || pm->width() <= 0 || pm->height() <= 0)
|
||||
return;
|
||||
|
||||
const int dpr_expected_width = qRound(expected_width * dpr);
|
||||
const int dpr_expected_height = qRound(expected_height * dpr);
|
||||
|
||||
if (pm->width() == dpr_expected_width &&
|
||||
pm->height() == dpr_expected_height &&
|
||||
pm->devicePixelRatio() == dpr &&
|
||||
opacity == 100.0f)
|
||||
{
|
||||
switch (scaling_mode)
|
||||
{
|
||||
case ScalingMode::Fit:
|
||||
case ScalingMode::Stretch:
|
||||
case ScalingMode::Center:
|
||||
return;
|
||||
|
||||
case ScalingMode::Fill:
|
||||
case ScalingMode::Tile:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QPixmap final_pixmap(dpr_expected_width, dpr_expected_height);
|
||||
final_pixmap.setDevicePixelRatio(dpr);
|
||||
final_pixmap.fill(Qt::transparent);
|
||||
|
||||
QPainter painter;
|
||||
painter.begin(&final_pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
painter.setOpacity(opacity / 100.0f);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
|
||||
const QRectF srcRect(0, 0, pm->width(), pm->height());
|
||||
const QRectF painterRect(0, 0, expected_width, expected_height);
|
||||
|
||||
switch (scaling_mode)
|
||||
{
|
||||
case ScalingMode::Fit:
|
||||
case ScalingMode::Fill:
|
||||
{
|
||||
auto const aspect_mode = (scaling_mode == ScalingMode::Fit) ?
|
||||
Qt::KeepAspectRatio :
|
||||
Qt::KeepAspectRatioByExpanding;
|
||||
|
||||
QSizeF scaledSize(pm->width(), pm->height());
|
||||
scaledSize.scale(dpr_expected_width, dpr_expected_height, aspect_mode);
|
||||
|
||||
*pm = pm->scaled(
|
||||
qRound(scaledSize.width()),
|
||||
qRound(scaledSize.height()),
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
);
|
||||
|
||||
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
|
||||
|
||||
QSizeF logicalSize = pm->size() / dpr;
|
||||
QRectF destRect(QPointF(0, 0), logicalSize);
|
||||
|
||||
destRect.moveCenter(painterRect.center());
|
||||
|
||||
painter.drawPixmap(destRect, *pm, scaledSrcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Stretch:
|
||||
{
|
||||
*pm = pm->scaled(
|
||||
dpr_expected_width,
|
||||
dpr_expected_height,
|
||||
Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
);
|
||||
|
||||
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
|
||||
|
||||
painter.drawPixmap(painterRect, *pm, scaledSrcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Center:
|
||||
{
|
||||
const qreal pmWidth = pm->width() / dpr;
|
||||
const qreal pmHeight = pm->height() / dpr;
|
||||
|
||||
QRectF destRect(0, 0, pmWidth, pmHeight);
|
||||
|
||||
destRect.moveCenter(painterRect.center());
|
||||
|
||||
painter.drawPixmap(destRect, *pm, srcRect);
|
||||
break;
|
||||
}
|
||||
case ScalingMode::Tile:
|
||||
{
|
||||
const qreal tileWidth = pm->width() / dpr;
|
||||
const qreal tileHeight = pm->height() / dpr;
|
||||
|
||||
if (tileWidth <= 0 || tileHeight <= 0)
|
||||
break;
|
||||
|
||||
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
tileSource.setDevicePixelRatio(dpr);
|
||||
|
||||
QBrush tileBrush(tileSource);
|
||||
tileBrush.setTextureImage(tileSource.toImage());
|
||||
|
||||
painter.fillRect(painterRect, tileBrush);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
painter.end();
|
||||
*pm = std::move(final_pixmap);
|
||||
}
|
||||
|
||||
void ShowInFileExplorer(QWidget* parent, const QFileInfo& file)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
@@ -254,68 +371,6 @@ namespace QtUtils
|
||||
widget->resize(width, height);
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
// Can't get a handle for an unmapped window in X, it doesn't like it.
|
||||
if (!widget->isVisible())
|
||||
{
|
||||
Console.WriteLn("Returning null window info for widget because it is not visible.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle());
|
||||
wi.window_handle = reinterpret_cast<void*>(widget->winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle());
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", widget->windowHandle());
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData());
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
const qreal dpr = widget->devicePixelRatioF();
|
||||
wi.surface_width = static_cast<u32>(static_cast<qreal>(widget->width()) * dpr);
|
||||
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi);
|
||||
if (!surface_refresh_rate.has_value())
|
||||
{
|
||||
// Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise.
|
||||
const QScreen* widget_screen = widget->screen();
|
||||
if (!widget_screen)
|
||||
widget_screen = QGuiApplication::primaryScreen();
|
||||
surface_refresh_rate = widget_screen ? static_cast<float>(widget_screen->refreshRate()) : 0.0f;
|
||||
}
|
||||
|
||||
wi.surface_refresh_rate = surface_refresh_rate.value();
|
||||
INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate);
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role, bool useQuotes)
|
||||
{
|
||||
QString csv;
|
||||
|
||||
@@ -9,11 +9,20 @@
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QAbstractItemModel>
|
||||
#include <QtGui/QGuiApplication>
|
||||
#include <QtGui/QWindow>
|
||||
#if !defined(_WIN32) and !defined(__APPLE__)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
#include <QtGui/QScreen>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
|
||||
#include "common/Console.h"
|
||||
|
||||
class ByteStream;
|
||||
|
||||
class QAction;
|
||||
@@ -49,6 +58,20 @@ namespace QtUtils
|
||||
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
|
||||
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths);
|
||||
|
||||
enum struct ScalingMode
|
||||
{
|
||||
Fit,
|
||||
Fill,
|
||||
Stretch,
|
||||
Center,
|
||||
Tile,
|
||||
|
||||
MaxCount
|
||||
};
|
||||
|
||||
/// Resize and scale a given Pixmap (and optionally adjust opacity)
|
||||
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity);
|
||||
|
||||
/// Returns a key id for a key event, including any modifiers that we need (e.g. Keypad).
|
||||
/// NOTE: Defined in QtKeyCodes.cpp, not QtUtils.cpp.
|
||||
u32 KeyEventToCode(const QKeyEvent* ev);
|
||||
@@ -83,8 +106,82 @@ namespace QtUtils
|
||||
/// Adjusts the fixed size for a window if it's not resizeable.
|
||||
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
|
||||
|
||||
/// Returns the common window info structure for a Qt widget.
|
||||
std::optional<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
|
||||
/// Returns the common window info structure for a Qt Window/Widget.
|
||||
template <class T>
|
||||
requires std::is_base_of_v<QWidget, T> || std::is_base_of_v<QWindow, T>
|
||||
std::optional<WindowInfo> GetWindowInfoForWindow(T* window)
|
||||
{
|
||||
WindowInfo wi;
|
||||
|
||||
// Windows and Apple are easy here since there's no display connection.
|
||||
#if defined(_WIN32)
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wi.type = WindowInfo::Type::MacOS;
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
|
||||
QWindow* windowHandle;
|
||||
if constexpr (std::is_base_of_v<QWidget, T>)
|
||||
windowHandle = window->windowHandle();
|
||||
else
|
||||
windowHandle = window;
|
||||
|
||||
if (platform_name == QStringLiteral("xcb"))
|
||||
{
|
||||
// Can't get a handle for an unmapped window in X, it doesn't like it.
|
||||
if (!window->isVisible())
|
||||
{
|
||||
Console.WriteLn("Returning null window info for widget because it is not visible.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wi.type = WindowInfo::Type::X11;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle);
|
||||
wi.window_handle = reinterpret_cast<void*>(window->winId());
|
||||
}
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
{
|
||||
wi.type = WindowInfo::Type::Wayland;
|
||||
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle);
|
||||
wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData());
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
qreal dpr;
|
||||
if constexpr (std::is_base_of_v<QWidget, T>)
|
||||
dpr = window->devicePixelRatioF();
|
||||
else
|
||||
dpr = window->devicePixelRatio();
|
||||
|
||||
wi.surface_width = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(window->width()) * dpr)), 1));
|
||||
wi.surface_height = static_cast<u32>(std::max(static_cast<int>(std::round(static_cast<qreal>(window->height()) * dpr)), 1));
|
||||
wi.surface_scale = static_cast<float>(dpr);
|
||||
|
||||
// Query refresh rate, we need it for sync.
|
||||
std::optional<float> surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi);
|
||||
if (!surface_refresh_rate.has_value())
|
||||
{
|
||||
// Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise.
|
||||
const QScreen* widget_screen = window->screen();
|
||||
if (!widget_screen)
|
||||
widget_screen = QGuiApplication::primaryScreen();
|
||||
surface_refresh_rate = widget_screen ? static_cast<float>(widget_screen->refreshRate()) : 0.0f;
|
||||
}
|
||||
|
||||
wi.surface_refresh_rate = surface_refresh_rate.value();
|
||||
INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate);
|
||||
|
||||
return wi;
|
||||
}
|
||||
|
||||
/// Converts a value to a QString of said value with a proper fixed width
|
||||
template <typename T>
|
||||
|
||||
@@ -127,7 +127,25 @@ void AchievementLoginDialog::processLoginResult(bool result, const QString& mess
|
||||
}
|
||||
}
|
||||
|
||||
done(0);
|
||||
// Show success messagebox
|
||||
const std::string username = Host::GetBaseStringSettingValue("Achievements", "Username");
|
||||
QMessageBox::information(
|
||||
this, tr("Login Successful"),
|
||||
tr("Successfully logged in to RetroAchievements as %1.").arg(QString::fromStdString(username)));
|
||||
|
||||
m_ui.status->setText(tr("Successfully logged in as %1.").arg(QString::fromStdString(username)));
|
||||
m_ui.status->setStyleSheet("color: green; font-weight: bold;");
|
||||
|
||||
disconnect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &AchievementLoginDialog::loginClicked);
|
||||
|
||||
m_login->setVisible(false);
|
||||
QPushButton* dismissButton = m_ui.buttonBox->addButton(tr("&Dismiss"), QDialogButtonBox::AcceptRole);
|
||||
dismissButton->setDefault(true);
|
||||
dismissButton->setFocus();
|
||||
|
||||
connect(dismissButton, &QPushButton::clicked, this, [this]() { done(0); });
|
||||
|
||||
enableUI(false);
|
||||
}
|
||||
|
||||
void AchievementLoginDialog::connectUi()
|
||||
|
||||
@@ -71,7 +71,21 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="saveDrawStats">
|
||||
<property name="text">
|
||||
<string>Save Draw Stats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="saveFrameStats">
|
||||
<property name="text">
|
||||
<string>Save Frame Stats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="saveTransferImages">
|
||||
<property name="text">
|
||||
<string>Save Transfer Image Data</string>
|
||||
|
||||
@@ -110,6 +110,8 @@ DebugSettingsWidget::DebugSettingsWidget(SettingsWindow* settings_dialog, QWidge
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveAlpha, "EmuCore/GS", "SaveAlpha", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveInfo, "EmuCore/GS", "SaveInfo", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveTransferImages, "EmuCore/GS", "SaveTransferImages", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveDrawStats, "EmuCore/GS", "SaveDrawStats", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_gs.saveFrameStats, "EmuCore/GS", "SaveFrameStats", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawStart, "EmuCore/GS", "SaveDrawStart", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveDrawCount, "EmuCore/GS", "SaveDrawCount", 5000);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_gs.saveFrameStart, "EmuCore/GS", "SaveFrameStart", 0);
|
||||
@@ -215,6 +217,8 @@ void DebugSettingsWidget::onDrawDumpingChanged()
|
||||
m_gs.saveAlpha->setEnabled(enabled);
|
||||
m_gs.saveInfo->setEnabled(enabled);
|
||||
m_gs.saveTransferImages->setEnabled(enabled);
|
||||
m_gs.saveDrawStats->setEnabled(enabled);
|
||||
m_gs.saveFrameStats->setEnabled(enabled);
|
||||
m_gs.saveDrawStart->setEnabled(enabled);
|
||||
m_gs.saveDrawCount->setEnabled(enabled);
|
||||
m_gs.saveFrameStart->setEnabled(enabled);
|
||||
|
||||
@@ -164,7 +164,6 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* settings_dialog
|
||||
dialog()->registerWidgetHelp(m_ui.rtcDateTime, tr("Real-Time Clock"), tr("Current date and time"),
|
||||
tr("Real-time clock (RTC) used by the virtual PlayStation 2.<br>"
|
||||
"This time is only applied upon booting the PS2; changing it while in-game will have no effect.<br>"
|
||||
"NOTE: This assumes you have your PS2 set to the default timezone of GMT+0 and default DST of Summer Time.<br>"
|
||||
"Some games require an RTC date/time set after their release date."));
|
||||
dialog()->registerWidgetHelp(m_ui.rtcUseSystemLocaleFormat, tr("Use System Locale Format"), tr("User Preference"),
|
||||
tr("Uses the operating system's date/time format rather than \"yyyy-MM-dd HH:mm:ss\". May exclude seconds."));
|
||||
|
||||
@@ -63,7 +63,7 @@ bool GameListSettingsWidget::addExcludedPath(const std::string& path)
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
m_ui.excludedPaths->addItem(QString::fromStdString(path));
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ void GameListSettingsWidget::addSearchDirectory(const QString& path, bool recurs
|
||||
Host::AddBaseValueToStringList("GameList", recursive ? "RecursivePaths" : "Paths", spath.c_str());
|
||||
Host::CommitBaseSettingChanges();
|
||||
refreshDirectoryList();
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::removeSearchDirectory(const QString& path)
|
||||
@@ -166,7 +166,7 @@ void GameListSettingsWidget::removeSearchDirectory(const QString& path)
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
refreshDirectoryList();
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onDirectoryListContextMenuRequested(const QPoint& point)
|
||||
@@ -261,7 +261,7 @@ void GameListSettingsWidget::onRemoveExcludedPathButtonClicked()
|
||||
|
||||
delete item;
|
||||
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onExcludedPathsSelectionChanged()
|
||||
@@ -271,10 +271,10 @@ void GameListSettingsWidget::onExcludedPathsSelectionChanged()
|
||||
|
||||
void GameListSettingsWidget::onRescanAllGamesClicked()
|
||||
{
|
||||
g_main_window->refreshGameList(true);
|
||||
g_main_window->refreshGameList(true, true);
|
||||
}
|
||||
|
||||
void GameListSettingsWidget::onScanForNewGamesClicked()
|
||||
{
|
||||
g_main_window->refreshGameList(false);
|
||||
g_main_window->refreshGameList(false, true);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ GameSummaryWidget::GameSummaryWidget(const GameList::Entry* entry, SettingsWindo
|
||||
connect(m_ui.inputProfile, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onInputProfileChanged);
|
||||
connect(m_ui.verify, &QAbstractButton::clicked, this, &GameSummaryWidget::onVerifyClicked);
|
||||
connect(m_ui.searchHash, &QAbstractButton::clicked, this, &GameSummaryWidget::onSearchHashClicked);
|
||||
connect(m_ui.checkWiki, &QAbstractButton::clicked, this, [this, entry]() { onCheckWikiClicked(entry); });
|
||||
connect(m_ui.checkWiki, &QAbstractButton::clicked, this,
|
||||
[this, serial = entry->serial]() { onCheckWikiClicked(serial); });
|
||||
|
||||
bool has_custom_title = false, has_custom_region = false;
|
||||
GameList::CheckCustomAttributesForPath(m_entry_path, has_custom_title, has_custom_region);
|
||||
@@ -280,7 +281,8 @@ void GameSummaryWidget::onVerifyClicked()
|
||||
Error error;
|
||||
if (!hasher.Open(m_entry_path, &error))
|
||||
{
|
||||
setVerifyResult(QString::fromStdString(error.GetDescription()));
|
||||
QString message(QString::fromStdString(error.GetDescription()));
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Error"), message);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,9 +367,9 @@ void GameSummaryWidget::onSearchHashClicked()
|
||||
QtUtils::OpenURL(this, fmt::format("http://redump.org/discs/quicksearch/{}", m_redump_search_keyword).c_str());
|
||||
}
|
||||
|
||||
void GameSummaryWidget::onCheckWikiClicked(const GameList::Entry* entry)
|
||||
void GameSummaryWidget::onCheckWikiClicked(const std::string& serial)
|
||||
{
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", entry->serial).c_str());
|
||||
QtUtils::OpenURL(this, fmt::format("https://wiki.pcsx2.net/{}", serial).c_str());
|
||||
}
|
||||
|
||||
void GameSummaryWidget::setVerifyResult(QString error)
|
||||
|
||||
@@ -26,7 +26,7 @@ private Q_SLOTS:
|
||||
void onDiscPathBrowseClicked();
|
||||
void onVerifyClicked();
|
||||
void onSearchHashClicked();
|
||||
void onCheckWikiClicked(const GameList::Entry* entry);
|
||||
void onCheckWikiClicked(const std::string& serial);
|
||||
|
||||
private:
|
||||
void populateInputProfiles();
|
||||
|
||||
@@ -970,7 +970,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
|
||||
m_texture.precacheTextureReplacements->setEnabled(enabled);
|
||||
}
|
||||
|
||||
|
||||
void GraphicsSettingsWidget::onCaptureContainerChanged()
|
||||
{
|
||||
const std::string container(
|
||||
|
||||
@@ -136,6 +136,16 @@
|
||||
<string>Aggressive</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Normal (Maintain Upscale)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Aggressive (Maintain Upscale)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
#include "Common.h"
|
||||
#include "Host.h"
|
||||
#include "MainWindow.h"
|
||||
#include "QtUtils.h"
|
||||
#include "SettingWidgetBinder.h"
|
||||
#include "SettingsWindow.h"
|
||||
#include "QtHost.h"
|
||||
|
||||
static const char* IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP(InterfaceSettingsWidget,
|
||||
"Supported Image Types (*.bmp *.gif *.jpg *.jpeg *.png *.webp)");
|
||||
|
||||
const char* InterfaceSettingsWidget::THEME_NAMES[] = {
|
||||
QT_TRANSLATE_NOOP("InterfaceSettingsWidget", "Native"),
|
||||
//: Ignore what Crowdin says in this string about "[Light]/[Dark]" being untouchable here, these are not variables in this case and must be translated.
|
||||
@@ -75,6 +73,17 @@ const char* InterfaceSettingsWidget::THEME_VALUES[] = {
|
||||
"Custom",
|
||||
nullptr};
|
||||
|
||||
const char* InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[] = {
|
||||
"fit",
|
||||
"fill",
|
||||
"stretch",
|
||||
"center",
|
||||
"tile",
|
||||
nullptr};
|
||||
|
||||
const char* InterfaceSettingsWidget::IMAGE_FILE_FILTER = QT_TRANSLATE_NOOP("InterfaceSettingsWidget",
|
||||
"Supported Image Types (*.bmp *.gif *.jpg *.jpeg *.png *.webp)");
|
||||
|
||||
InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
|
||||
: SettingsWidget(settings_dialog, parent)
|
||||
{
|
||||
@@ -123,12 +132,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
QtHost::GetDefaultThemeName(), "InterfaceSettingsWidget");
|
||||
connect(m_ui.theme, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit themeChanged(); });
|
||||
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.backgroundOpacity, "UI", "GameListBackgroundOpacity", 100);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.backgroundFill, "UI", "GameListBackgroundFill", false);
|
||||
SettingWidgetBinder::BindWidgetToFloatSetting(sif, m_ui.backgroundOpacity, "UI", "GameListBackgroundOpacity", 100.0f);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.backgroundScale, "UI", "GameListBackgroundMode", BACKGROUND_SCALE_NAMES, QtUtils::ScalingMode::Fit);
|
||||
connect(m_ui.backgroundBrowse, &QPushButton::clicked, [this]() { onSetGameListBackgroundTriggered(); });
|
||||
connect(m_ui.backgroundReset, &QPushButton::clicked, [this]() { onClearGameListBackgroundTriggered(); });
|
||||
connect(m_ui.backgroundOpacity, &QSpinBox::valueChanged, [this]() { emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundFill, &QCheckBox::checkStateChanged, [this]() {emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundOpacity, &QSpinBox::editingFinished, [this]() { emit backgroundChanged(); });
|
||||
connect(m_ui.backgroundScale, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]() { emit backgroundChanged(); });
|
||||
|
||||
populateLanguages();
|
||||
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.language, "UI", "Language", QtHost::GetDefaultLanguage());
|
||||
@@ -200,7 +209,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
tr("Shows the game you are currently playing as part of your profile in Discord."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.mouseLock, tr("Enable Mouse Lock"), tr("Unchecked"),
|
||||
tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b><br><b>Limited support for mixed-resolution with non-100% DPI configurations.</b>"));
|
||||
tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b>"));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.doubleClickTogglesFullscreen, tr("Double-Click Toggles Fullscreen"), tr("Checked"),
|
||||
tr("Allows switching in and out of fullscreen mode by double-clicking the game window."));
|
||||
@@ -212,7 +221,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
tr("Automatically starts Big Picture Mode instead of the regular Qt interface when PCSX2 launches."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundBrowse, tr("Game List Background"), tr("None"),
|
||||
tr("Enable an animated / static background on the game list (where you launch your games).<br>"
|
||||
tr("Enable an animated/static background on the game list (where you launch your games).<br>"
|
||||
"This background is only visible in the library and will be hidden once a game is launched. It will also be paused when it's not in focus."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundReset, tr("Disable/Reset Game List Background"), tr("None"),
|
||||
@@ -221,8 +230,9 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
|
||||
m_ui.backgroundOpacity, tr("Game List Background Opacity"), tr("100%"),
|
||||
tr("Sets the opacity of the custom background."));
|
||||
dialog()->registerWidgetHelp(
|
||||
m_ui.backgroundFill, tr("Fill Image"), tr("Unchecked"),
|
||||
tr("Expand the image to fill all available background area."));
|
||||
m_ui.backgroundScale, tr("Background Image Scaling"), tr("Fit"),
|
||||
tr("Select how to display the background image: <br><br>Fit (Preserve aspect ratio, fit to screen)"
|
||||
"<br>Fill (Preserve aspect ratio, fill the screen) <br>Stretch (Ignore aspect ratio) <br>Center (Centers the image without any scaling) <br>Tile (Repeat the image to fill the screen)"));
|
||||
|
||||
onRenderToSeparateWindowChanged();
|
||||
}
|
||||
@@ -249,19 +259,14 @@ void InterfaceSettingsWidget::onSetGameListBackgroundTriggered()
|
||||
return;
|
||||
|
||||
std::string relative_path = Path::MakeRelative(QDir::toNativeSeparators(path).toStdString(), EmuFolders::DataRoot);
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListBackgroundEnabled", true);
|
||||
Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str());
|
||||
|
||||
if (!Host::ContainsBaseSettingValue("UI", "GameListBackgroundOpacity"))
|
||||
Host::SetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f);
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
emit backgroundChanged();
|
||||
}
|
||||
|
||||
void InterfaceSettingsWidget::onClearGameListBackgroundTriggered()
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListBackgroundEnabled", false);
|
||||
Host::RemoveBaseSettingValue("UI", "GameListBackgroundPath");
|
||||
Host::CommitBaseSettingChanges();
|
||||
emit backgroundChanged();
|
||||
|
||||
@@ -33,4 +33,6 @@ private:
|
||||
public:
|
||||
static const char* THEME_NAMES[];
|
||||
static const char* THEME_VALUES[];
|
||||
static const char* BACKGROUND_SCALE_NAMES[];
|
||||
static const char* IMAGE_FILE_FILTER;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>725</width>
|
||||
<height>617</height>
|
||||
<height>625</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@@ -224,9 +224,6 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="backgroundOpacity">
|
||||
<property name="wrapping">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="buttonSymbols">
|
||||
<enum>QAbstractSpinBox::ButtonSymbols::PlusMinus</enum>
|
||||
</property>
|
||||
@@ -251,10 +248,54 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="backgroundFill">
|
||||
<property name="text">
|
||||
<string>Fill Image</string>
|
||||
<widget class="QLabel" name="backgroundScaleLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scaling:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>backgroundScale</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="backgroundScale">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fit</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Fill</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Stretch</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Center</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Tile</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -351,7 +392,7 @@
|
||||
<tabstop>backgroundBrowse</tabstop>
|
||||
<tabstop>backgroundReset</tabstop>
|
||||
<tabstop>backgroundOpacity</tabstop>
|
||||
<tabstop>backgroundFill</tabstop>
|
||||
<tabstop>backgroundScale</tabstop>
|
||||
<tabstop>autoUpdateTag</tabstop>
|
||||
<tabstop>autoUpdateEnabled</tabstop>
|
||||
<tabstop>checkForUpdates</tabstop>
|
||||
|
||||
@@ -138,7 +138,7 @@ void MemoryCardSettingsWidget::autoSizeUI()
|
||||
void MemoryCardSettingsWidget::tryInsertCard(u32 slot, const QString& newCard)
|
||||
{
|
||||
// handle where the card is dragged in from explorer or something
|
||||
const int lastSlashPos = std::max(newCard.lastIndexOf('/'), newCard.lastIndexOf('\\'));
|
||||
const qsizetype lastSlashPos = std::max(newCard.lastIndexOf('/'), newCard.lastIndexOf('\\'));
|
||||
const std::string newCardStr(
|
||||
(lastSlashPos >= 0) ? newCard.mid(0, lastSlashPos).toStdString() : newCard.toStdString());
|
||||
if (newCardStr.empty())
|
||||
|
||||
@@ -133,7 +133,7 @@ void QtHost::InstallTranslator(QWidget* dialog_parent)
|
||||
if (!has_base_ts)
|
||||
{
|
||||
// Try without the country suffix.
|
||||
const int index = language.lastIndexOf('-');
|
||||
const qsizetype index = language.lastIndexOf('-');
|
||||
if (index > 0)
|
||||
{
|
||||
base_path = QStringLiteral("%1/qt_%2.qm").arg(base_dir).arg(language.left(index));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -484,6 +484,13 @@ bool Achievements::CreateClient(rc_client_t** client, std::unique_ptr<HTTPDownlo
|
||||
|
||||
rc_client_set_userdata(new_client, http->get());
|
||||
|
||||
const std::string custom_host = Host::GetBaseStringSettingValue("Achievements", "Host", "");
|
||||
if (!custom_host.empty())
|
||||
{
|
||||
Console.WriteLn("Achievements: Using custom host %s", custom_host.c_str());
|
||||
rc_client_set_host(new_client, custom_host.c_str());
|
||||
}
|
||||
|
||||
*client = new_client;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
|
||||
cdvdStruct cdvd;
|
||||
|
||||
s64 PSXCLK = 36864000;
|
||||
u32 PSXCLK = 36864000;
|
||||
|
||||
static constexpr s32 GMT9_OFFSET_SECONDS = 9 * 60 * 60; // 32400
|
||||
|
||||
static constexpr u8 monthmap[13] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
@@ -920,69 +922,92 @@ void cdvdReset()
|
||||
cdvd.ReadTime = cdvdBlockReadTime(MODE_DVDROM);
|
||||
cdvd.RotSpeed = cdvdRotationTime(MODE_DVDROM);
|
||||
|
||||
ReadOSDConfigParames();
|
||||
|
||||
// Print time zone offset, DST, time format, date format, and system time basis.
|
||||
DevCon.WriteLn(Color_StrongGreen, configParams1.timezoneOffset < 0 ? "Time Zone Offset: GMT%03d:%02d" : "Time Zone Offset: GMT+%02d:%02d",
|
||||
configParams1.timezoneOffset / 60, std::abs(configParams1.timezoneOffset % 60));
|
||||
DevCon.WriteLn(Color_StrongGreen, "DST: %s Time", configParams2.daylightSavings ? "Summer" : "Winter");
|
||||
DevCon.WriteLn(Color_StrongGreen, "Time Format: %s-Hour", configParams2.timeFormat ? "12" : "24");
|
||||
DevCon.WriteLn(Color_StrongGreen, "Date Format: %s", configParams2.dateFormat ? (configParams2.dateFormat == 2 ? "DD/MM/YYYY" : "MM/DD/YYYY") : "YYYY/MM/DD");
|
||||
DevCon.WriteLn(Color_StrongGreen, "System Time Basis: %s",
|
||||
EmuConfig.ManuallySetRealTimeClock ? "Manual RTC" : g_InputRecording.isActive() ? "Default Input Recording Time" : "Operating System Time");
|
||||
|
||||
std::tm input_tm{};
|
||||
std::tm resulting_tm{};
|
||||
|
||||
const int bios_settings_offset_seconds = 60 * (configParams1.timezoneOffset + configParams2.daylightSavings * 60);
|
||||
|
||||
// CDVD internally uses GMT+9, 1-indexed months, and year offset of 2000 instead of 1900.
|
||||
// tm struct uses 0-indexed months and year offset of 1900.
|
||||
if (EmuConfig.ManuallySetRealTimeClock)
|
||||
{
|
||||
// Convert to GMT+9 (assumes GMT+0)
|
||||
std::tm tm{};
|
||||
tm.tm_sec = EmuConfig.RtcSecond;
|
||||
tm.tm_min = EmuConfig.RtcMinute;
|
||||
tm.tm_hour = EmuConfig.RtcHour;
|
||||
tm.tm_mday = EmuConfig.RtcDay;
|
||||
tm.tm_mon = EmuConfig.RtcMonth - 1;
|
||||
tm.tm_year = EmuConfig.RtcYear + 100; // 2000 - 1900
|
||||
tm.tm_isdst = 1;
|
||||
resulting_tm.tm_sec = EmuConfig.RtcSecond;
|
||||
resulting_tm.tm_min = EmuConfig.RtcMinute;
|
||||
resulting_tm.tm_hour = EmuConfig.RtcHour;
|
||||
resulting_tm.tm_mday = EmuConfig.RtcDay;
|
||||
resulting_tm.tm_mon = EmuConfig.RtcMonth - 1;
|
||||
resulting_tm.tm_year = EmuConfig.RtcYear + 100;
|
||||
resulting_tm.tm_isdst = 0;
|
||||
|
||||
// Need this instead of mktime for timezone independence
|
||||
std::time_t t = 0;
|
||||
#if defined(_WIN32)
|
||||
t = _mkgmtime(&tm) + 32400; //60 * 60 * 9 for GMT+9
|
||||
gmtime_s(&tm, &t);
|
||||
#else
|
||||
t = timegm(&tm) + 32400;
|
||||
gmtime_r(&t, &tm);
|
||||
#endif
|
||||
|
||||
cdvd.RTC.second = tm.tm_sec;
|
||||
cdvd.RTC.minute = tm.tm_min;
|
||||
cdvd.RTC.hour = tm.tm_hour;
|
||||
cdvd.RTC.day = tm.tm_mday;
|
||||
cdvd.RTC.month = tm.tm_mon + 1;
|
||||
cdvd.RTC.year = tm.tm_year - 100;
|
||||
// Work backwards to input time by accounting for BIOS settings and GMT+9 defaultism.
|
||||
#if defined(_WIN32)
|
||||
const std::time_t input_time = _mkgmtime(&resulting_tm) + GMT9_OFFSET_SECONDS - bios_settings_offset_seconds;
|
||||
gmtime_s(&input_tm, &input_time);
|
||||
#else
|
||||
const std::time_t input_time = timegm(&resulting_tm) + GMT9_OFFSET_SECONDS - bios_settings_offset_seconds;
|
||||
gmtime_r(&input_time, &input_tm);
|
||||
#endif
|
||||
}
|
||||
// If we are recording, always use the same RTC setting
|
||||
// for games that use the RTC to seed their RNG -- this is very important to be the same everytime!
|
||||
else if (g_InputRecording.isActive())
|
||||
{
|
||||
Console.WriteLn("Input Recording Active - Using Constant RTC of 04-03-2020 (DD-MM-YYYY)");
|
||||
// Why not just 0 everything? Some games apparently require the date to be valid in terms of when
|
||||
// the PS2 / Game actually came out. (MGS3). So set it to a value well beyond any PS2 game's release date.
|
||||
cdvd.RTC.second = 0;
|
||||
cdvd.RTC.minute = 0;
|
||||
cdvd.RTC.hour = 0;
|
||||
cdvd.RTC.day = 4;
|
||||
cdvd.RTC.month = 3;
|
||||
cdvd.RTC.year = 20;
|
||||
// Default input recording value (2020-03-04 00:00:00) if manual RTC is off. Well beyond any PS2 game's release date.
|
||||
// Some games require a valid date in terms of when the PS2 / game actually came out (see: MGS3).
|
||||
// Changing this will ruin compat with old input recordings (RNG seeding).
|
||||
input_tm.tm_sec = 0;
|
||||
input_tm.tm_min = 0;
|
||||
input_tm.tm_hour = 0;
|
||||
input_tm.tm_mday = 4;
|
||||
input_tm.tm_mon = 2;
|
||||
input_tm.tm_year = 120;
|
||||
input_tm.tm_isdst = 0;
|
||||
|
||||
#if defined(_WIN32)
|
||||
const std::time_t resulting_time = _mkgmtime(&input_tm) - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
|
||||
gmtime_s(&resulting_tm, &resulting_time);
|
||||
#else
|
||||
const std::time_t resulting_time = timegm(&input_tm) - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
|
||||
gmtime_r(&resulting_time, &resulting_tm);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// CDVD internally uses GMT+9. If you think the time's wrong, you're wrong.
|
||||
// Set up your time zone and winter/summer in the BIOS. No PS2 BIOS I know of features automatic DST.
|
||||
const std::time_t utc_time = std::time(nullptr);
|
||||
const std::time_t gmt9_time = (utc_time + 32400); //60 * 60 * 9
|
||||
struct tm curtime = {};
|
||||
// User must set time zone and winter/summer DST in the BIOS for correct time.
|
||||
const std::time_t input_time = std::time(nullptr) + GMT9_OFFSET_SECONDS;
|
||||
const std::time_t resulting_time = input_time - GMT9_OFFSET_SECONDS + bios_settings_offset_seconds;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
gmtime_s(&curtime, &gmt9_time);
|
||||
gmtime_s(&input_tm, &input_time);
|
||||
gmtime_s(&resulting_tm, &resulting_time);
|
||||
#else
|
||||
gmtime_r(&gmt9_time, &curtime);
|
||||
gmtime_r(&input_time, &input_tm);
|
||||
gmtime_r(&resulting_time, &resulting_tm);
|
||||
#endif
|
||||
cdvd.RTC.second = static_cast<u8>(curtime.tm_sec);
|
||||
cdvd.RTC.minute = static_cast<u8>(curtime.tm_min);
|
||||
cdvd.RTC.hour = static_cast<u8>(curtime.tm_hour);
|
||||
cdvd.RTC.day = static_cast<u8>(curtime.tm_mday);
|
||||
cdvd.RTC.month = static_cast<u8>(curtime.tm_mon + 1); // WX returns Jan as "0"
|
||||
cdvd.RTC.year = static_cast<u8>(curtime.tm_year - 100); // offset from 2000
|
||||
}
|
||||
|
||||
// Send completed input time to the CDVD.
|
||||
cdvd.RTC.second = static_cast<u8>(input_tm.tm_sec);
|
||||
cdvd.RTC.minute = static_cast<u8>(input_tm.tm_min);
|
||||
cdvd.RTC.hour = static_cast<u8>(input_tm.tm_hour);
|
||||
cdvd.RTC.day = static_cast<u8>(input_tm.tm_mday);
|
||||
cdvd.RTC.month = static_cast<u8>(input_tm.tm_mon + 1);
|
||||
cdvd.RTC.year = static_cast<u8>(input_tm.tm_year - 100);
|
||||
|
||||
// Print time that will appear in the user's BIOS rather than input time.
|
||||
DevCon.WriteLn(Color_StrongGreen, "Resulting System Time: 20%02u-%02u-%02u %02u:%02u:%02u",
|
||||
resulting_tm.tm_year - 100, resulting_tm.tm_mon + 1, resulting_tm.tm_mday,
|
||||
resulting_tm.tm_hour, resulting_tm.tm_min, resulting_tm.tm_sec);
|
||||
|
||||
cdvdCtrlTrayClose();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <ctype.h>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <time.h>
|
||||
|
||||
#include "fmt/format.h"
|
||||
@@ -270,6 +271,23 @@ static void DetectDiskType()
|
||||
|
||||
static std::string m_SourceFilename[3];
|
||||
static CDVD_SourceType m_CurrentSourceType = CDVD_SourceType::NoDisc;
|
||||
static std::mutex s_cdvd_lock;
|
||||
|
||||
bool cdvdLock(Error* error)
|
||||
{
|
||||
if (!s_cdvd_lock.try_lock())
|
||||
{
|
||||
Error::SetString(error, TRANSLATE_STR("CDVD", "The CDVD system is currently in use."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cdvdUnlock()
|
||||
{
|
||||
s_cdvd_lock.unlock();
|
||||
}
|
||||
|
||||
void CDVDsys_SetFile(CDVD_SourceType srctype, std::string newfile)
|
||||
{
|
||||
@@ -579,7 +597,7 @@ static s32 NODISCdummyS32()
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void NODISCnewDiskCB(void (*/* callback */)())
|
||||
static void NODISCnewDiskCB(void (* /* callback */)())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ struct cdvdTrackIndex
|
||||
u8 discM; // current minute location on the disc (BCD encoded)
|
||||
u8 discS; // current sector location on the disc (BCD encoded)
|
||||
u8 discF; // current frame location on the disc (BCD encoded)
|
||||
|
||||
};
|
||||
|
||||
struct cdvdTrack
|
||||
@@ -188,6 +187,13 @@ extern u8 strack;
|
||||
extern u8 etrack;
|
||||
extern std::array<cdvdTrack, 100> tracks;
|
||||
|
||||
/// Try to take the CDVD lock, return false if it's already in use.
|
||||
/// Must be called before your first CDVD call.
|
||||
extern bool cdvdLock(Error* error = nullptr);
|
||||
|
||||
/// Release the CDVD lock. Must be called after you're done with CDVD.
|
||||
extern void cdvdUnlock();
|
||||
|
||||
extern void CDVDsys_ChangeSource(CDVD_SourceType type);
|
||||
extern void CDVDsys_SetFile(CDVD_SourceType srctype, std::string newfile);
|
||||
extern const std::string& CDVDsys_GetFile(CDVD_SourceType srctype);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "common/Error.h"
|
||||
#include "common/MD5Digest.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
@@ -39,6 +38,10 @@ bool IsoHasher::Open(std::string iso_path, Error* error)
|
||||
{
|
||||
Close();
|
||||
|
||||
m_is_locked = cdvdLock(error);
|
||||
if (!m_is_locked)
|
||||
return false;
|
||||
|
||||
CDVDsys_SetFile(CDVD_SourceType::Iso, std::move(iso_path));
|
||||
CDVDsys_ChangeSource(CDVD_SourceType::Iso);
|
||||
|
||||
@@ -103,6 +106,12 @@ bool IsoHasher::Open(std::string iso_path, Error* error)
|
||||
|
||||
void IsoHasher::Close()
|
||||
{
|
||||
if (!m_is_locked)
|
||||
return;
|
||||
|
||||
cdvdUnlock();
|
||||
m_is_locked = false;
|
||||
|
||||
if (!m_is_open)
|
||||
return;
|
||||
|
||||
@@ -151,7 +160,7 @@ bool IsoHasher::ComputeTrackHash(Track& track, ProgressCallback* callback)
|
||||
const u32 update_interval = std::max<u32>(track.sectors / 100u, 1u);
|
||||
callback->SetStatusText(
|
||||
fmt::format(TRANSLATE_FS("CDVD", "Calculating checksum for track {}..."), track.number).c_str());
|
||||
callback->SetProgressRange(track.sectors);
|
||||
callback->SetProgressRange(track.sectors);
|
||||
|
||||
MD5Digest md5;
|
||||
for (u32 i = 0; i < track.sectors; i++)
|
||||
|
||||
@@ -28,6 +28,9 @@ public:
|
||||
IsoHasher();
|
||||
~IsoHasher();
|
||||
|
||||
IsoHasher(const IsoHasher&) = delete;
|
||||
IsoHasher& operator=(const IsoHasher&) = delete;
|
||||
|
||||
static std::string_view GetTrackTypeString(u32 type);
|
||||
|
||||
u32 GetTrackCount() const { return static_cast<u32>(m_tracks.size()); }
|
||||
@@ -44,6 +47,7 @@ private:
|
||||
bool ComputeTrackHash(Track& track, ProgressCallback* callback);
|
||||
|
||||
std::vector<Track> m_tracks;
|
||||
bool m_is_locked = false;
|
||||
bool m_is_open = false;
|
||||
bool m_is_cd = false;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
static const u32 BIAS = 2; // Bus is half of the actual ps2 speed
|
||||
static const u32 PS2CLK = 294912000; //hz /* 294.912 mhz */
|
||||
extern s64 PSXCLK; /* 36.864 Mhz */
|
||||
extern u32 PSXCLK; /* 36.864 Mhz */
|
||||
|
||||
|
||||
#include "Memory.h"
|
||||
@@ -21,4 +21,4 @@ extern s64 PSXCLK; /* 36.864 Mhz */
|
||||
#include <string>
|
||||
|
||||
extern std::string ShiftJIS_ConvertString( const char* src );
|
||||
extern std::string ShiftJIS_ConvertString( const char* src, int maxlen );
|
||||
extern std::string ShiftJIS_ConvertString( const char* src, int maxlen );
|
||||
|
||||
@@ -456,6 +456,8 @@ enum class GSNativeScaling : u8
|
||||
Off,
|
||||
Normal,
|
||||
Aggressive,
|
||||
NormalUpscaled,
|
||||
AggressiveUpscaled,
|
||||
MaxCount
|
||||
};
|
||||
|
||||
@@ -711,7 +713,7 @@ struct Pcsx2Config
|
||||
|
||||
union
|
||||
{
|
||||
u64 bitset[2];
|
||||
u64 bitsets[2];
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -776,6 +778,8 @@ struct Pcsx2Config
|
||||
SaveAlpha : 1,
|
||||
SaveInfo : 1,
|
||||
SaveTransferImages : 1,
|
||||
SaveDrawStats : 1,
|
||||
SaveFrameStats : 1,
|
||||
DumpReplaceableTextures : 1,
|
||||
DumpReplaceableMipmaps : 1,
|
||||
DumpTexturesWithFMVActive : 1,
|
||||
@@ -1314,7 +1318,7 @@ struct Pcsx2Config
|
||||
InhibitScreensaver : 1,
|
||||
BackupSavestate : 1,
|
||||
McdFolderAutoManage : 1,
|
||||
ManuallySetRealTimeClock : 1,
|
||||
ManuallySetRealTimeClock : 1, // passes user-set real-time clock information to cdvd at startup
|
||||
UseSystemLocaleFormat : 1, // presents OS time format instead of yyyy-MM-dd HH:mm:ss for manual RTC
|
||||
|
||||
HostFs : 1,
|
||||
|
||||
@@ -252,7 +252,7 @@ static void vSyncInfoCalc(vSyncTimingInfo* info, double framesPerSecond, u32 sca
|
||||
const u64 accumilatedHRenderError = (hRender % 10000) + (hBlank % 10000);
|
||||
const u64 accumilatedHFractional = accumilatedHRenderError % 10000;
|
||||
info->hRender += (u32)(accumilatedHRenderError / 10000);
|
||||
info->hSyncError = (accumilatedHFractional * (scansPerFrame / (IsInterlacedVideoMode() ? 2 : 1))) / 10000;
|
||||
info->hSyncError = (u32)((accumilatedHFractional * (scansPerFrame / (IsInterlacedVideoMode() ? 2 : 1))) / 10000);
|
||||
|
||||
// Note: In NTSC modes there is some small rounding error in the vsync too,
|
||||
// however it would take thousands of frames for it to amount to anything and
|
||||
|
||||
@@ -105,7 +105,7 @@ bool ATA::IO_Write()
|
||||
{
|
||||
IO_SparseCacheUpdateLocation(imagePos + written);
|
||||
// Align to sparse block size.
|
||||
u32 writeSize = hddSparseBlockSize - ((imagePos + written) % hddSparseBlockSize);
|
||||
u32 writeSize = static_cast<u32>(hddSparseBlockSize - ((imagePos + written) % hddSparseBlockSize));
|
||||
// Limit to size of write.
|
||||
writeSize = std::min(writeSize, entry.length - written);
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace InternalServers
|
||||
//Counts
|
||||
ret->questions = dns.questions;
|
||||
|
||||
DNS_State* state = new DNS_State(reqs.size(), reqs, ret, payload->sourcePort);
|
||||
DNS_State* state = new DNS_State(static_cast<int>(reqs.size()), reqs, ret, payload->sourcePort);
|
||||
outstandingQueries++;
|
||||
|
||||
for (size_t i = 0; i < reqs.size(); i++)
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "..\net.h"
|
||||
using namespace std;
|
||||
|
||||
class TAPAdapter : public NetAdapter
|
||||
{
|
||||
|
||||
@@ -8,22 +8,19 @@
|
||||
|
||||
std::vector<std::unique_ptr<BiosThread>> getEEThreads()
|
||||
{
|
||||
std::vector<std::unique_ptr<BiosThread>> threads;
|
||||
|
||||
if (CurrentBiosInformation.eeThreadListAddr <= 0)
|
||||
return threads;
|
||||
if (!VMManager::HasValidVM() || CurrentBiosInformation.eeThreadListAddr == 0)
|
||||
return {};
|
||||
|
||||
const u32 start = CurrentBiosInformation.eeThreadListAddr & 0x3fffff;
|
||||
|
||||
std::vector<std::unique_ptr<BiosThread>> threads;
|
||||
for (int tid = 0; tid < 256; tid++)
|
||||
{
|
||||
EEInternalThread* internal = static_cast<EEInternalThread*>(
|
||||
PSM(start + tid * sizeof(EEInternalThread)));
|
||||
|
||||
EEInternalThread* internal = static_cast<EEInternalThread*>(PSM(start + tid * sizeof(EEInternalThread)));
|
||||
if (internal->status != (int)ThreadStatus::THS_BAD)
|
||||
{
|
||||
auto thread = std::make_unique<EEThread>(tid, *internal);
|
||||
threads.push_back(std::move(thread));
|
||||
}
|
||||
if (internal && internal->status != (int)ThreadStatus::THS_BAD)
|
||||
threads.emplace_back(std::make_unique<EEThread>(tid, *internal));
|
||||
}
|
||||
|
||||
return threads;
|
||||
@@ -63,8 +60,7 @@ std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
|
||||
|
||||
data.PC = iopMemRead32(data.SavedSP + 0x8c);
|
||||
|
||||
auto thread = std::make_unique<IOPThread>(data);
|
||||
threads.push_back(std::move(thread));
|
||||
threads.emplace_back(std::make_unique<IOPThread>(data));
|
||||
|
||||
item = iopMemRead32(item + 0x24);
|
||||
}
|
||||
@@ -93,7 +89,7 @@ std::vector<IopMod> getIOPModules()
|
||||
if (i > 1000)
|
||||
return {};
|
||||
|
||||
IopMod mod;
|
||||
IopMod& mod = modlist.emplace_back();
|
||||
|
||||
u32 nstr = iopMemRead32(maddr + 4);
|
||||
if (nstr)
|
||||
@@ -113,8 +109,6 @@ std::vector<IopMod> getIOPModules()
|
||||
mod.data_size = iopMemRead32(maddr + 0x20);
|
||||
mod.bss_size = iopMemRead32(maddr + 0x24);
|
||||
|
||||
modlist.push_back(mod);
|
||||
|
||||
maddr = iopMemRead32(maddr);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,9 @@ namespace MIPSAnalyst
|
||||
bool suspectedNoReturn = false;
|
||||
|
||||
u32 addr;
|
||||
for (addr = startAddr; addr <= endAddr; addr += 4) {
|
||||
for (u64 i = startAddr; i <= endAddr; i += 4) {
|
||||
addr = static_cast<u32>(i);
|
||||
|
||||
// Use pre-existing symbol map info if available. May be more reliable.
|
||||
ccc::FunctionHandle existing_symbol_handle = database.functions.first_handle_from_starting_address(addr);
|
||||
const ccc::Function* existing_symbol = database.functions.symbol_from_handle(existing_symbol_handle);
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace MipsStackWalk
|
||||
|
||||
if (entry == INVALIDTARGET)
|
||||
{
|
||||
stop = std::max<s64>(0, (s64)start - LONGEST_FUNCTION);
|
||||
stop = static_cast<u32>(std::max<s64>(0, (s64)start - LONGEST_FUNCTION));
|
||||
}
|
||||
|
||||
for (u32 pc = start; cpu->isValidAddress(pc) && pc >= stop; pc -= 4)
|
||||
|
||||
@@ -163,6 +163,9 @@ void SymbolImporter::Reset()
|
||||
|
||||
void SymbolImporter::LoadAndAnalyseElf(Pcsx2Config::DebugAnalysisOptions options)
|
||||
{
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
const std::string& elf_path = VMManager::GetCurrentELF();
|
||||
|
||||
Error error;
|
||||
@@ -192,6 +195,9 @@ void SymbolImporter::AnalyseElf(
|
||||
Pcsx2Config::DebugAnalysisOptions options,
|
||||
bool wait_until_elf_is_loaded)
|
||||
{
|
||||
if (!VMManager::HasValidVM())
|
||||
return;
|
||||
|
||||
// Search for a .sym file to load symbols from.
|
||||
std::string nocash_path;
|
||||
CDVD_SourceType source_type = CDVDsys_GetSourceType();
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
<!-- PDF METADATA STARTS ---
|
||||
title: "PCSX2 - Debugger Documentation"
|
||||
date: "2021"
|
||||
footer-left: "[Document Source](https://github.com/PCSX2/pcsx2/blob/{LATEST-GIT-TAG}/pcsx2/Docs/Debugger.md)"
|
||||
urlcolor: "cyan"
|
||||
... PDF METADATA ENDS -->
|
||||
|
||||
# Debugger Key Bindings
|
||||
|
||||
## Disassembly View
|
||||
- `G` - goto
|
||||
- `E` - edit breakpoint
|
||||
- `D` - enable/disable breakpoint
|
||||
- `B` - add breakpoint
|
||||
- `M` - assemble opcode
|
||||
- `Right Arrow` - follow branch/position memory view to accessed address
|
||||
- `Left Arrow` - go back one branch level/goto pc
|
||||
- `Up Arrow` - move cursor up one line
|
||||
- `Down Arrow` - move cursor down one line
|
||||
- `Page Up` - move visible area up one page
|
||||
- `Page Down` - move visible area down one page
|
||||
- `F10` - step over
|
||||
- `F11` - step into
|
||||
- `Tab` - toggle display symbols
|
||||
- `Left Click` - select line/toggle breakpoint if line is already highlighted
|
||||
- `Right Click` - open context menu
|
||||
|
||||
## Memory View
|
||||
|
||||
- `G` - goto
|
||||
- `Ctrl+B` - add breakpoint
|
||||
- `Left Arrow` - move cursor back one byte/nibble
|
||||
- `Right Arrow` - move cursor ahead one byte/nibble
|
||||
- `Up Arrow` - move cursor up one line
|
||||
- `Down Arrow` - move cursor down one line
|
||||
- `Page Up` - move cursor up one page
|
||||
- `Page Down` - move cursor down one page
|
||||
- `0-9,A-F` - overwrite hex nibble
|
||||
- `any` - overwrite ansi byte
|
||||
- `Left Click` - select byte/nibble
|
||||
- `Right Click` - open context menu
|
||||
- `Ctrl+Mouse Wheel` - zoom memory view
|
||||
- `Esc` - return to previous goto address
|
||||
- `Ctrl+V` - paste a hex string into memory
|
||||
- `Enter/Return` - edit selected float
|
||||
|
||||
## Breakpoint List
|
||||
|
||||
- `Up Arrow` - select previous item
|
||||
- `Down Arrow` - select next item
|
||||
- `Delete` - remove selected breakpoint
|
||||
- `Enter/Return` - edit selected breakpoint
|
||||
- `Space` - toggle enable state of selected breakpoint
|
||||
@@ -180,7 +180,7 @@ The clamp modes are also numerically based.
|
||||
* skipDrawStart [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* skipDrawEnd [Value between `0` to `10000`] {0-10000} Default: Off (`0`)
|
||||
* halfPixelOffset [`0` or `1` or `2` or `3` or `4` or `5`] {Off, Normal Vertex, Special (Texture), Special (Texture Aggressive), Align to Native, Align to Native with Texture Offsets} Default: Off (`0`)
|
||||
* nativeScaling [`0` or `1` or `2`] {Normal, Aggressive or Off} Default: Normal (`0`)
|
||||
* nativeScaling [`0` or `1` or `2` or `3` or `4`] {Off, Normal, Aggressive, Normal (Maintain Upscale) or Aggressive (Maintain Upscale) } Default: Normal (`0`)
|
||||
* nativePaletteDraw [`0` or `1`] {Off, On} Default: Off (`0`)
|
||||
* roundSprite [`0` or `1` or `2`] {Off, Half or Full} Default: Off (`0`)
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
"nativeScaling": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
"maximum": 4
|
||||
},
|
||||
"roundSprite": {
|
||||
"type": "integer",
|
||||
|
||||
128
pcsx2/GS/GS.cpp
128
pcsx2/GS/GS.cpp
@@ -185,8 +185,8 @@ static void GSClampUpscaleMultiplier(Pcsx2Config::GSOptions& config)
|
||||
if (config.UpscaleMultiplier <= static_cast<float>(max_upscale_multiplier))
|
||||
{
|
||||
// Shouldn't happen, but just in case.
|
||||
if (config.UpscaleMultiplier < 1.0f)
|
||||
config.UpscaleMultiplier = 1.0f;
|
||||
if (config.UpscaleMultiplier < 0.0f)
|
||||
config.UpscaleMultiplier = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -664,40 +664,36 @@ void GSgetStats(SmallStringBase& info)
|
||||
|
||||
if (pps >= 170000000)
|
||||
{
|
||||
pps /= 1073741824; // Gpps
|
||||
pps /= _1gb; // Gpps
|
||||
prefix = 'G';
|
||||
}
|
||||
else if (pps >= 35000000)
|
||||
{
|
||||
pps /= 1048576; // Mpps
|
||||
pps /= _1mb; // Mpps
|
||||
prefix = 'M';
|
||||
}
|
||||
else if (pps >= 1024)
|
||||
else if (pps >= _1kb)
|
||||
{
|
||||
pps /= 1024;
|
||||
prefix = 'K';
|
||||
}
|
||||
else
|
||||
{
|
||||
prefix = '\0';
|
||||
pps /= _1kb; // kpps
|
||||
prefix = 'k';
|
||||
}
|
||||
|
||||
info.format("{} SW | {} SP | {} P | {} D | {:.2f} S | {:.2f} U | {:.2f} {}pps",
|
||||
info.format("{} SW | {} SYNP | {} PRIM | {} DRW | {:.2f} SWIZ | {:.2f} UNSWIZ | {:.2f} {}pps",
|
||||
api_name,
|
||||
(int)pm.Get(GSPerfMon::SyncPoint),
|
||||
(int)pm.Get(GSPerfMon::Prim),
|
||||
(int)pm.Get(GSPerfMon::Draw),
|
||||
pm.Get(GSPerfMon::Swizzle) / 1024,
|
||||
pm.Get(GSPerfMon::Unswizzle) / 1024,
|
||||
pm.Get(GSPerfMon::Swizzle) / _1kb,
|
||||
pm.Get(GSPerfMon::Unswizzle) / _1kb,
|
||||
pps, prefix);
|
||||
}
|
||||
else if (GSCurrentRenderer == GSRendererType::Null)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "{} Null", api_name);
|
||||
info.format("{} Null", api_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.format("{} HW | {} P | {} D | {} DC | {} B | {} RP | {} RB | {} TC | {} TU",
|
||||
info.format("{} HW | {} PRIM | {} DRW | {} DRWC | {} BAR | {} RP | {} RB | {} TC | {} TU",
|
||||
api_name,
|
||||
(int)pm.Get(GSPerfMon::Prim),
|
||||
(int)pm.Get(GSPerfMon::Draw),
|
||||
@@ -713,30 +709,45 @@ void GSgetStats(SmallStringBase& info)
|
||||
void GSgetMemoryStats(SmallStringBase& info)
|
||||
{
|
||||
if (!g_texture_cache)
|
||||
{
|
||||
info.assign("");
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 targets = g_texture_cache->GetTargetMemoryUsage();
|
||||
const u64 sources = g_texture_cache->GetSourceMemoryUsage();
|
||||
const u64 hashcache = g_texture_cache->GetHashCacheMemoryUsage();
|
||||
const u64 pool = g_gs_device->GetPoolMemoryUsage();
|
||||
const u64 total = targets + sources + hashcache + pool;
|
||||
// Get megabyte values. Round negligible values to 0.1 MB to avoid swamping.
|
||||
const auto get_MB = [](const double bytes) {
|
||||
return (bytes <= 0.0 ? bytes : std::max(0.1, bytes / static_cast<double>(_1mb)));
|
||||
};
|
||||
|
||||
const auto format_precision = [](const double megabytes) -> std::string {
|
||||
return (megabytes < 10.0 ?
|
||||
fmt::format("{:.1f}", megabytes) :
|
||||
fmt::format("{:.0f}", std::round(megabytes)));
|
||||
};
|
||||
|
||||
const double targets_MB = get_MB(static_cast<double>(g_texture_cache->GetTargetMemoryUsage()));
|
||||
const double sources_MB = get_MB(static_cast<double>(g_texture_cache->GetSourceMemoryUsage()));
|
||||
const double pool_MB = get_MB(static_cast<double>(g_gs_device->GetPoolMemoryUsage()));
|
||||
|
||||
if (GSConfig.TexturePreloading == TexturePreloadingLevel::Full)
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "VRAM: {} MB | T: {} MB | S: {} MB | H: {} MB | P: {} MB",
|
||||
(int)std::ceil(total / 1048576.0f),
|
||||
(int)std::ceil(targets / 1048576.0f),
|
||||
(int)std::ceil(sources / 1048576.0f),
|
||||
(int)std::ceil(hashcache / 1048576.0f),
|
||||
(int)std::ceil(pool / 1048576.0f));
|
||||
const double hashcache_MB = get_MB(static_cast<double>(g_texture_cache->GetHashCacheMemoryUsage()));
|
||||
const double total_MB = targets_MB + sources_MB + hashcache_MB + pool_MB;
|
||||
info.format("VRAM: {} MB | TGT: {} MB | SRC: {} MB | HC: {} MB | PL: {} MB",
|
||||
format_precision(total_MB),
|
||||
format_precision(targets_MB),
|
||||
format_precision(sources_MB),
|
||||
format_precision(hashcache_MB),
|
||||
format_precision(pool_MB));
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::format_to(std::back_inserter(info), "VRAM: {} MB | T: {} MB | S: {} MB | P: {} MB",
|
||||
(int)std::ceil(total / 1048576.0f),
|
||||
(int)std::ceil(targets / 1048576.0f),
|
||||
(int)std::ceil(sources / 1048576.0f),
|
||||
(int)std::ceil(pool / 1048576.0f));
|
||||
const double total_MB = targets_MB + sources_MB + pool_MB;
|
||||
info.format("VRAM: {} MB | TGT: {} MB | SRC: {} MB | PL: {} MB",
|
||||
format_precision(total_MB),
|
||||
format_precision(targets_MB),
|
||||
format_precision(sources_MB),
|
||||
format_precision(pool_MB));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1067,15 +1078,52 @@ std::pair<u8, u8> GSGetRGBA8AlphaMinMax(const void* data, u32 width, u32 height,
|
||||
static_cast<u8>(maxc.maxv_u32() >> 24));
|
||||
}
|
||||
|
||||
static void HotkeyAdjustUpscaleMultiplier(s32 delta)
|
||||
static void HotkeyAdjustUpscaleMultiplier(const float delta)
|
||||
{
|
||||
const u32 new_multiplier = static_cast<u32>(std::clamp(static_cast<s32>(EmuConfig.GS.UpscaleMultiplier) + delta, 1, 8));
|
||||
Host::AddKeyedOSDMessage("UpscaleMultiplierChanged",
|
||||
fmt::format(TRANSLATE_FS("GS", "Upscale multiplier set to {}x."), new_multiplier), Host::OSD_QUICK_DURATION);
|
||||
EmuConfig.GS.UpscaleMultiplier = new_multiplier;
|
||||
if (!g_gs_renderer)
|
||||
return;
|
||||
|
||||
// this is pretty slow. we only really need to flush the TC and recompile shaders.
|
||||
if (GSCurrentRenderer == GSRendererType::SW || GSCurrentRenderer == GSRendererType::Null)
|
||||
{
|
||||
Host::AddIconOSDMessage("UpscaleMultiplierChanged", ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE,
|
||||
TRANSLATE_STR("GS", "Upscaling can only be changed while using the Hardware Renderer."), Host::OSD_QUICK_DURATION);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clamp logic mirrors GraphicsSettingsWidget::populateUpscaleMultipliers().
|
||||
float candidate_multiplier = EmuConfig.GS.UpscaleMultiplier + delta;
|
||||
const float max_multiplier = static_cast<float>(std::clamp(GSGetMaxUpscaleMultiplier(g_gs_device->GetMaxTextureSize()),
|
||||
10u, EmuConfig.GS.ExtendedUpscalingMultipliers ? 25u : 12u));
|
||||
|
||||
std::string osd_message;
|
||||
if (candidate_multiplier <= 1)
|
||||
{
|
||||
candidate_multiplier = 1;
|
||||
osd_message = TRANSLATE_STR("GS", "Upscale multiplier set to native resolution.");
|
||||
}
|
||||
else if (candidate_multiplier >= max_multiplier)
|
||||
{
|
||||
candidate_multiplier = max_multiplier;
|
||||
osd_message = fmt::format(TRANSLATE_FS("GS", "Upscale multiplier maximized to {}x."), max_multiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
osd_message = fmt::format(TRANSLATE_FS("GS", "Upscale multiplier {} to {}x."),
|
||||
delta > 0 ? TRANSLATE_STR("GS", "increased") : TRANSLATE_STR("GS", "decreased"), candidate_multiplier);
|
||||
}
|
||||
|
||||
// Need to calculate our own target resolution. Reading after applying settings is a race condition.
|
||||
const GSVector2i base_resolution = g_gs_renderer ? g_gs_renderer->PCRTCDisplays.GetResolution() : GSVector2i(0, 0);
|
||||
const int target_iwidth = static_cast<int>(std::round(static_cast<float>(base_resolution.x) * candidate_multiplier));
|
||||
const int target_iheight = static_cast<int>(std::round(static_cast<float>(base_resolution.y) * candidate_multiplier));
|
||||
|
||||
//: Leftmost value is an OSD message about the upscale multiplier. Values in parentheses are a resolution width (left) and height (right).
|
||||
Host::AddIconOSDMessage("UpscaleMultiplierChanged", ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE,
|
||||
fmt::format(TRANSLATE_FS("GS", "{} ({} x {})"), osd_message, target_iwidth, target_iheight), Host::OSD_QUICK_DURATION);
|
||||
|
||||
// This is pretty slow. We only really need to flush the TC and recompile shaders.
|
||||
// TODO(Stenzek): Make it faster at some point in the future.
|
||||
EmuConfig.GS.UpscaleMultiplier = candidate_multiplier;
|
||||
MTGS::ApplySettings();
|
||||
}
|
||||
|
||||
@@ -1147,13 +1195,13 @@ BEGIN_HOTKEY_LIST(g_gs_hotkeys){"Screenshot", TRANSLATE_NOOP("Hotkeys", "Graphic
|
||||
TRANSLATE_NOOP("Hotkeys", "Increase Upscale Multiplier"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed)
|
||||
HotkeyAdjustUpscaleMultiplier(1);
|
||||
HotkeyAdjustUpscaleMultiplier(1.0f);
|
||||
}},
|
||||
{"DecreaseUpscaleMultiplier", TRANSLATE_NOOP("Hotkeys", "Graphics"),
|
||||
TRANSLATE_NOOP("Hotkeys", "Decrease Upscale Multiplier"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed)
|
||||
HotkeyAdjustUpscaleMultiplier(-1);
|
||||
HotkeyAdjustUpscaleMultiplier(-1.0f);
|
||||
}},
|
||||
{"ToggleOSD", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle On-Screen Display"),
|
||||
[](s32 pressed) {
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#include "GSPerfMon.h"
|
||||
#include "GS.h"
|
||||
#include "GSUtil.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <inttypes.h>
|
||||
|
||||
GSPerfMon g_perfmon;
|
||||
|
||||
@@ -41,3 +43,28 @@ void GSPerfMon::Update()
|
||||
|
||||
memset(m_counters, 0, sizeof(m_counters));
|
||||
}
|
||||
|
||||
GSPerfMon GSPerfMon::operator-(const GSPerfMon& other)
|
||||
{
|
||||
GSPerfMon diff;
|
||||
for (std::size_t i = 0; i < std::size(diff.m_counters); i++)
|
||||
{
|
||||
diff.m_counters[i] = m_counters[i] - other.m_counters[i];
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
void GSPerfMon::Dump(const std::string& filename, bool hw)
|
||||
{
|
||||
FILE* fp = fopen(filename.c_str(), "w");
|
||||
if (!fp)
|
||||
return;
|
||||
|
||||
std::size_t last = hw ? CounterLastHW : CounterLastSW;
|
||||
for (std::size_t i = 0; i < last; i++)
|
||||
{
|
||||
fprintf(fp, "%s: %" PRIu64 "\n", GSUtil::GetPerfMonCounterName(static_cast<counter_t>(i), hw), static_cast<u64>(m_counters[i]));
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "common/Pcsx2Defs.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
class GSPerfMon
|
||||
{
|
||||
@@ -27,12 +28,15 @@ public:
|
||||
// Reused counters for HW.
|
||||
TextureCopies = Fillrate,
|
||||
TextureUploads = SyncPoint,
|
||||
|
||||
CounterLastHW = CounterLast,
|
||||
CounterLastSW = SyncPoint + 1
|
||||
};
|
||||
|
||||
protected:
|
||||
double m_counters[CounterLast] = {};
|
||||
double m_stats[CounterLast] = {};
|
||||
u64 m_frame = 0;
|
||||
int m_frame = 0;
|
||||
clock_t m_lastframe = 0;
|
||||
int m_count = 0;
|
||||
int m_disp_fb_sprite_blits = 0;
|
||||
@@ -42,8 +46,8 @@ public:
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetFrame(u64 frame) { m_frame = frame; }
|
||||
u64 GetFrame() { return m_frame; }
|
||||
void SetFrame(int frame) { m_frame = frame; }
|
||||
int GetFrame() { return m_frame; }
|
||||
void EndFrame(bool frame_only);
|
||||
|
||||
void Put(counter_t c, double val) { m_counters[c] += val; }
|
||||
@@ -58,6 +62,10 @@ public:
|
||||
m_disp_fb_sprite_blits = 0;
|
||||
return blits;
|
||||
}
|
||||
|
||||
GSPerfMon operator-(const GSPerfMon& other);
|
||||
|
||||
void Dump(const std::string& filename, bool hw);
|
||||
};
|
||||
|
||||
extern GSPerfMon g_perfmon;
|
||||
extern GSPerfMon g_perfmon;
|
||||
|
||||
@@ -202,6 +202,9 @@ void GSState::Reset(bool hardware_reset)
|
||||
m_backed_up_ctx = -1;
|
||||
|
||||
memcpy(&m_prev_env, &m_env, sizeof(m_prev_env));
|
||||
|
||||
m_perfmon_draw.Reset();
|
||||
m_perfmon_frame.Reset();
|
||||
}
|
||||
|
||||
template<bool auto_flush>
|
||||
@@ -2125,6 +2128,16 @@ void GSState::FlushPrim()
|
||||
g_perfmon.Put(GSPerfMon::Draw, 1);
|
||||
g_perfmon.Put(GSPerfMon::Prim, m_index.tail / GSUtil::GetVertexCount(PRIM->PRIM));
|
||||
|
||||
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
{
|
||||
if (GSConfig.SaveDrawStats)
|
||||
{
|
||||
m_perfmon_draw = g_perfmon - m_perfmon_draw;
|
||||
m_perfmon_draw.Dump(GetDrawDumpPath("%05d_draw_stats.txt", s_n), GSIsHardwareRenderer());
|
||||
m_perfmon_draw = g_perfmon;
|
||||
}
|
||||
}
|
||||
|
||||
m_index.tail = 0;
|
||||
m_vertex.head = 0;
|
||||
|
||||
@@ -2429,8 +2442,8 @@ void GSState::InitReadFIFO(u8* mem, int len)
|
||||
if (GSConfig.SaveRT && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
{
|
||||
const std::string s(GetDrawDumpPath(
|
||||
"%05d_read_%05x_%d_%d_%d_%d_%d_%d.bmp",
|
||||
s_n, (int)m_env.BITBLTBUF.SBP, (int)m_env.BITBLTBUF.SBW, (int)m_env.BITBLTBUF.SPSM,
|
||||
"%05d_read_%05x_%d_%s_%d_%d_%d_%d.bmp",
|
||||
s_n, (int)m_env.BITBLTBUF.SBP, (int)m_env.BITBLTBUF.SBW, GSUtil::GetPSMName(m_env.BITBLTBUF.SPSM),
|
||||
r.left, r.top, r.right, r.bottom));
|
||||
|
||||
m_mem.SaveBMP(s, m_env.BITBLTBUF.SBP, m_env.BITBLTBUF.SBW, m_env.BITBLTBUF.SPSM, r.right, r.bottom);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "GS/GS.h"
|
||||
#include "GS/GSPerfMon.h"
|
||||
#include "GS/GSLocalMemory.h"
|
||||
#include "GS/GSDrawingContext.h"
|
||||
#include "GS/GSDrawingEnvironment.h"
|
||||
@@ -264,6 +265,9 @@ public:
|
||||
static int s_last_transfer_draw_n;
|
||||
static int s_transfer_n;
|
||||
|
||||
GSPerfMon m_perfmon_frame; // Track stat across a frame.
|
||||
GSPerfMon m_perfmon_draw; // Track stat across a draw.
|
||||
|
||||
static constexpr u32 STATE_VERSION = 9;
|
||||
|
||||
enum REG_DIRTY
|
||||
|
||||
@@ -176,6 +176,40 @@ const char* GSUtil::GetACName(u32 ac)
|
||||
return (ac < std::size(names)) ? names[ac] : "";
|
||||
}
|
||||
|
||||
const char* GSUtil::GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw)
|
||||
{
|
||||
if (hw)
|
||||
{
|
||||
static constexpr const char* names_hw[GSPerfMon::CounterLastHW] = {
|
||||
"Prim",
|
||||
"Draw",
|
||||
"DrawCalls",
|
||||
"Readbacks",
|
||||
"Swizzle",
|
||||
"Unswizzle",
|
||||
"TextureCopies",
|
||||
"TextureUploads",
|
||||
"Barriers",
|
||||
"RenderPasses"
|
||||
};
|
||||
return counter < std::size(names_hw) ? names_hw[counter] : "";
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr const char* names_sw[GSPerfMon::CounterLastSW] = {
|
||||
"Prim",
|
||||
"Draw",
|
||||
"DrawCalls",
|
||||
"Readbacks",
|
||||
"Swizzle",
|
||||
"Unswizzle",
|
||||
"Fillrate",
|
||||
"SyncPoint"
|
||||
};
|
||||
return counter < std::size(names_sw) ? names_sw[counter] : "";
|
||||
}
|
||||
}
|
||||
|
||||
const u32* GSUtil::HasSharedBitsPtr(u32 dpsm)
|
||||
{
|
||||
return s_maps.SharedBitsField[dpsm];
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "GS.h"
|
||||
#include "GSRegs.h"
|
||||
#include "GSPerfMon.h"
|
||||
|
||||
class GSUtil
|
||||
{
|
||||
@@ -25,6 +26,7 @@ public:
|
||||
static const char* GetTFXName(u32 tfx);
|
||||
static const char* GetTCCName(u32 tcc);
|
||||
static const char* GetACName(u32 ac);
|
||||
static const char* GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw = true);
|
||||
|
||||
static const u32* HasSharedBitsPtr(u32 dpsm);
|
||||
static bool HasSharedBits(u32 spsm, const u32* ptr);
|
||||
|
||||
@@ -723,6 +723,39 @@ GSTexture* GSDevice::CreateTexture(int w, int h, int mipmap_levels, GSTexture::F
|
||||
return FetchSurface(GSTexture::Type::Texture, w, h, levels, format, false, m_features.prefer_new_textures && !prefer_reuse);
|
||||
}
|
||||
|
||||
void GSDevice::DoStretchRectWithAssertions(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear)
|
||||
{
|
||||
pxAssert((dTex && dTex->IsDepthStencil()) == HasDepthOutput(shader));
|
||||
pxAssert(linear ? SupportsBilinear(shader) : SupportsNearest(shader));
|
||||
GL_INS("StretchRect(%d) {%d,%d} %dx%d -> {%d,%d) %dx%d", shader, int(sRect.left), int(sRect.top),
|
||||
int(sRect.right - sRect.left), int(sRect.bottom - sRect.top), int(dRect.left), int(dRect.top),
|
||||
int(dRect.right - dRect.left), int(dRect.bottom - dRect.top));
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, cms, shader, linear);
|
||||
}
|
||||
|
||||
void GSDevice::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
bool red, bool green, bool blue, bool alpha, ShaderConvert shader)
|
||||
{
|
||||
GSHWDrawConfig::ColorMaskSelector cms;
|
||||
|
||||
cms.wr = red;
|
||||
cms.wg = green;
|
||||
cms.wb = blue;
|
||||
cms.wa = alpha;
|
||||
|
||||
pxAssert(HasVariableWriteMask(shader));
|
||||
GL_INS("ColorCopy Red:%d Green:%d Blue:%d Alpha:%d", cms.wr, cms.wg, cms.wb, cms.wa);
|
||||
|
||||
DoStretchRectWithAssertions(sTex, sRect, dTex, dRect, cms, shader, false);
|
||||
}
|
||||
|
||||
void GSDevice::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
ShaderConvert shader, bool linear)
|
||||
{
|
||||
DoStretchRectWithAssertions(sTex, sRect, dTex, dRect, GSHWDrawConfig::ColorMaskSelector(ShaderConvertWriteMask(shader)), shader, linear);
|
||||
}
|
||||
|
||||
void GSDevice::StretchRect(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader, bool linear)
|
||||
{
|
||||
StretchRect(sTex, GSVector4(0, 0, 1, 1), dTex, dRect, shader, linear);
|
||||
@@ -734,11 +767,11 @@ void GSDevice::DrawMultiStretchRects(
|
||||
for (u32 i = 0; i < num_rects; i++)
|
||||
{
|
||||
const MultiStretchRect& sr = rects[i];
|
||||
pxAssert(shader == ShaderConvert::COPY || shader == ShaderConvert::RTA_CORRECTION || rects[0].wmask.wrgba == 0xf);
|
||||
pxAssert(HasVariableWriteMask(shader) || rects[0].wmask.wrgba == 0xf);
|
||||
if (rects[0].wmask.wrgba != 0xf)
|
||||
{
|
||||
g_gs_device->StretchRect(sr.src, sr.src_rect, dTex, sr.dst_rect, rects[0].wmask.wr,
|
||||
rects[0].wmask.wg, rects[0].wmask.wb, rects[0].wmask.wa);
|
||||
rects[0].wmask.wg, rects[0].wmask.wb, rects[0].wmask.wa, shader);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -69,6 +69,27 @@ enum class ShaderInterlace
|
||||
Count
|
||||
};
|
||||
|
||||
static inline bool HasVariableWriteMask(ShaderConvert shader)
|
||||
{
|
||||
switch (shader)
|
||||
{
|
||||
case ShaderConvert::COPY:
|
||||
case ShaderConvert::RTA_CORRECTION:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int GetShaderIndexForMask(ShaderConvert shader, int mask)
|
||||
{
|
||||
pxAssert(HasVariableWriteMask(shader));
|
||||
int index = mask;
|
||||
if (shader == ShaderConvert::RTA_CORRECTION)
|
||||
index |= 1 << 4;
|
||||
return index;
|
||||
}
|
||||
|
||||
static inline bool HasDepthOutput(ShaderConvert shader)
|
||||
{
|
||||
switch (shader)
|
||||
@@ -549,6 +570,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
constexpr ColorMaskSelector(): key(0xF) {}
|
||||
constexpr ColorMaskSelector(u8 c): key(0) { wrgba = c; }
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
struct alignas(16) VSConstantBuffer
|
||||
{
|
||||
@@ -560,11 +582,11 @@ struct alignas(16) GSHWDrawConfig
|
||||
GSVector2i max_depth;
|
||||
__fi VSConstantBuffer()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
memset(static_cast<void*>(this), 0, sizeof(*this));
|
||||
}
|
||||
__fi VSConstantBuffer(const VSConstantBuffer& other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
}
|
||||
__fi VSConstantBuffer& operator=(const VSConstantBuffer& other)
|
||||
{
|
||||
@@ -584,7 +606,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
if (*this == other)
|
||||
return false;
|
||||
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -609,11 +631,11 @@ struct alignas(16) GSHWDrawConfig
|
||||
|
||||
__fi PSConstantBuffer()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
memset(static_cast<void*>(this), 0, sizeof(*this));
|
||||
}
|
||||
__fi PSConstantBuffer(const PSConstantBuffer& other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
}
|
||||
__fi PSConstantBuffer& operator=(const PSConstantBuffer& other)
|
||||
{
|
||||
@@ -633,7 +655,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
if (*this == other)
|
||||
return false;
|
||||
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -922,6 +944,12 @@ protected:
|
||||
/// Perform texture operations for ImGui
|
||||
void UpdateImGuiTextures();
|
||||
|
||||
protected:
|
||||
// Entry point to the renderer-specific StretchRect code.
|
||||
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) = 0;
|
||||
void DoStretchRectWithAssertions(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear);
|
||||
|
||||
public:
|
||||
GSDevice();
|
||||
virtual ~GSDevice();
|
||||
@@ -1037,9 +1065,9 @@ public:
|
||||
virtual std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) = 0;
|
||||
|
||||
virtual void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) = 0;
|
||||
virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) = 0;
|
||||
virtual void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY) = 0;
|
||||
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY);
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true);
|
||||
void StretchRect(GSTexture* sTex, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true);
|
||||
|
||||
/// Performs a screen blit for display. If dTex is null, it assumes you are writing to the system framebuffer/swap chain.
|
||||
|
||||
@@ -591,6 +591,13 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
|
||||
if (GSConfig.SaveTransferImages)
|
||||
DumpTransferImages();
|
||||
|
||||
if (GSConfig.SaveFrameStats)
|
||||
{
|
||||
m_perfmon_frame = g_perfmon - m_perfmon_frame;
|
||||
m_perfmon_frame.Dump(GetDrawDumpPath("%05d_f%05lld_frame_stats.txt", s_n, g_perfmon.GetFrame()), GSIsHardwareRenderer());
|
||||
m_perfmon_frame = g_perfmon;
|
||||
}
|
||||
}
|
||||
|
||||
const int fb_sprite_blits = g_perfmon.GetDisplayFramebufferSpriteBlits();
|
||||
|
||||
@@ -1274,28 +1274,18 @@ void GSDevice11::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r,
|
||||
dTex->SetState(GSTexture::State::Dirty);
|
||||
}
|
||||
|
||||
void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader, bool linear)
|
||||
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear)
|
||||
{
|
||||
pxAssert(dTex->IsDepthStencil() == HasDepthOutput(shader));
|
||||
pxAssert(linear ? SupportsBilinear(shader) : SupportsNearest(shader));
|
||||
StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), nullptr,
|
||||
m_convert.bs[ShaderConvertWriteMask(shader)].get(), linear);
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), nullptr, m_convert.bs[cms.wrgba].get(), linear);
|
||||
}
|
||||
|
||||
void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, bool linear)
|
||||
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, bool linear)
|
||||
{
|
||||
StretchRect(sTex, sRect, dTex, dRect, ps, ps_cb, m_convert.bs[D3D11_COLOR_WRITE_ENABLE_ALL].get(), linear);
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, ps, ps_cb, m_convert.bs[D3D11_COLOR_WRITE_ENABLE_ALL].get(), linear);
|
||||
}
|
||||
|
||||
void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader)
|
||||
{
|
||||
const u8 index = static_cast<u8>(red) | (static_cast<u8>(green) << 1) | (static_cast<u8>(blue) << 2) |
|
||||
(static_cast<u8>(alpha) << 3);
|
||||
StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), nullptr,
|
||||
m_convert.bs[index].get(), false);
|
||||
}
|
||||
|
||||
void GSDevice11::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear)
|
||||
void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear)
|
||||
{
|
||||
CommitClear(sTex);
|
||||
|
||||
@@ -1439,7 +1429,7 @@ void GSDevice11::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u
|
||||
|
||||
const GSVector4 dRect(0, 0, dSize, 1);
|
||||
const ShaderConvert shader = (dSize == 16) ? ShaderConvert::CLUT_4 : ShaderConvert::CLUT_8;
|
||||
StretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
}
|
||||
|
||||
void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
|
||||
@@ -1457,7 +1447,7 @@ void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offs
|
||||
|
||||
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
|
||||
const ShaderConvert shader = ((SPSM & 0xE) == 0) ? ShaderConvert::RGBA_TO_8I : ShaderConvert::RGB5A1_TO_8I;
|
||||
StretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
}
|
||||
|
||||
void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect)
|
||||
@@ -1465,18 +1455,19 @@ void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
|
||||
struct Uniforms
|
||||
{
|
||||
float weight;
|
||||
float pad0[3];
|
||||
float step_multiplier;
|
||||
float pad0[2];
|
||||
GSVector2i clamp_min;
|
||||
int downsample_factor;
|
||||
int pad1;
|
||||
};
|
||||
|
||||
const Uniforms cb = {
|
||||
static_cast<float>(downsample_factor * downsample_factor), {}, clamp_min, static_cast<int>(downsample_factor), 0};
|
||||
static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast<int>(downsample_factor), 0};
|
||||
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
|
||||
|
||||
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;
|
||||
StretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, m_convert.ps[static_cast<int>(shader)].get(), m_merge.cb.get(), nullptr, false);
|
||||
}
|
||||
|
||||
void GSDevice11::DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader)
|
||||
@@ -1595,7 +1586,7 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
// Save 2nd output
|
||||
if (feedback_write_2)
|
||||
{
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
DoStretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
m_merge.cb.get(), nullptr, linear);
|
||||
}
|
||||
|
||||
@@ -1606,12 +1597,12 @@ void GSDevice11::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
|
||||
if (sTex[0])
|
||||
{
|
||||
// 1st output is enabled. It must be blended
|
||||
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), linear);
|
||||
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge.ps[PMODE.MMOD].get(), m_merge.cb.get(), m_merge.bs.get(), linear);
|
||||
}
|
||||
|
||||
if (feedback_write_1)
|
||||
{
|
||||
StretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
DoStretchRect(dTex, full_r, sTex[2], dRect[2], m_convert.ps[static_cast<int>(ShaderConvert::YUV)].get(),
|
||||
m_merge.cb.get(), nullptr, linear);
|
||||
}
|
||||
}
|
||||
@@ -1620,7 +1611,7 @@ void GSDevice11::DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
{
|
||||
m_ctx->UpdateSubresource(m_interlace.cb.get(), 0, nullptr, &cb, 0, 0);
|
||||
|
||||
StretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast<int>(shader)].get(), m_interlace.cb.get(), linear);
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast<int>(shader)].get(), m_interlace.cb.get(), linear);
|
||||
}
|
||||
|
||||
void GSDevice11::DoFXAA(GSTexture* sTex, GSTexture* dTex)
|
||||
@@ -1646,7 +1637,7 @@ void GSDevice11::DoFXAA(GSTexture* sTex, GSTexture* dTex)
|
||||
return;
|
||||
}
|
||||
|
||||
StretchRect(sTex, sRect, dTex, dRect, m_fxaa_ps.get(), nullptr, true);
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, m_fxaa_ps.get(), nullptr, true);
|
||||
}
|
||||
|
||||
void GSDevice11::DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4])
|
||||
@@ -1658,7 +1649,7 @@ void GSDevice11::DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float para
|
||||
|
||||
m_ctx->UpdateSubresource(m_shadeboost.cb.get(), 0, nullptr, params, 0, 0);
|
||||
|
||||
StretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps.get(), m_shadeboost.cb.get(), false);
|
||||
DoStretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps.get(), m_shadeboost.cb.get(), false);
|
||||
}
|
||||
|
||||
void GSDevice11::SetupVS(VSSelector sel, const GSHWDrawConfig::VSConstantBuffer* cb)
|
||||
@@ -2157,7 +2148,7 @@ void GSDevice11::RenderImGui()
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &m_state.vb_stride, &vb_offset);
|
||||
}
|
||||
|
||||
void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, SetDATM datm)
|
||||
void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox)
|
||||
{
|
||||
// sfex3 (after the capcom logo), vf4 (first menu fading in), ffxii shadows, rumble roses shadows, persona4 shadows
|
||||
|
||||
@@ -2178,6 +2169,17 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vert
|
||||
|
||||
// ia
|
||||
|
||||
const GSVector4 src = GSVector4(bbox) / GSVector4(ds->GetSize()).xyxy();
|
||||
const GSVector4 dst = src * 2.0f - 1.0f;
|
||||
|
||||
const GSVertexPT1 vertices[] =
|
||||
{
|
||||
{GSVector4(dst.x, -dst.y, 0.5f, 1.0f), GSVector2(src.x, src.y)},
|
||||
{GSVector4(dst.z, -dst.y, 0.5f, 1.0f), GSVector2(src.z, src.y)},
|
||||
{GSVector4(dst.x, -dst.w, 0.5f, 1.0f), GSVector2(src.x, src.w)},
|
||||
{GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)},
|
||||
};
|
||||
|
||||
IASetVertexBuffer(vertices, sizeof(vertices[0]), 4);
|
||||
IASetInputLayout(m_convert.il.get());
|
||||
IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
||||
@@ -2446,7 +2448,7 @@ void GSDevice11::PSUnbindConflictingSRVs(GSTexture* tex1, GSTexture* tex2)
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < m_state.ps_sr_views.size(); i++)
|
||||
{
|
||||
if ((tex1 && m_state.ps_sr_views[i] == *(GSTexture11*)tex1) || (tex2 && m_state.ps_sr_views[i] == *(GSTexture11*)tex2))
|
||||
if ((tex1 && m_state.ps_sr_views[i] == *static_cast<GSTexture11*>(tex1)) || (tex2 && m_state.ps_sr_views[i] == *static_cast<GSTexture11*>(tex2)))
|
||||
{
|
||||
m_state.ps_sr_views[i] = nullptr;
|
||||
changed = true;
|
||||
@@ -2623,6 +2625,8 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
}
|
||||
|
||||
// Destination Alpha Setup
|
||||
const bool multidraw_fb_copy = m_features.multidraw_fb_copy && (config.require_one_barrier || config.require_full_barrier);
|
||||
GSTexture* primid_texture = nullptr;
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
|
||||
{
|
||||
@@ -2633,25 +2637,12 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
return;
|
||||
}
|
||||
|
||||
StretchRect(colclip_rt ? colclip_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(),
|
||||
DoStretchRect(colclip_rt ? colclip_rt : config.rt, GSVector4(config.drawarea) / GSVector4(rtsize).xyxy(),
|
||||
primid_texture, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast<u8>(config.datm)].get(), nullptr, false);
|
||||
}
|
||||
else if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil ||
|
||||
config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne)
|
||||
{
|
||||
const GSVector4 src = GSVector4(config.drawarea) / GSVector4(config.ds->GetSize()).xyxy();
|
||||
const GSVector4 dst = src * 2.0f - 1.0f;
|
||||
|
||||
GSVertexPT1 vertices[] =
|
||||
{
|
||||
{GSVector4(dst.x, -dst.y, 0.5f, 1.0f), GSVector2(src.x, src.y)},
|
||||
{GSVector4(dst.z, -dst.y, 0.5f, 1.0f), GSVector2(src.z, src.y)},
|
||||
{GSVector4(dst.x, -dst.w, 0.5f, 1.0f), GSVector2(src.x, src.w)},
|
||||
{GSVector4(dst.z, -dst.w, 0.5f, 1.0f), GSVector2(src.z, src.w)},
|
||||
};
|
||||
|
||||
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, vertices, config.datm);
|
||||
}
|
||||
(config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && !multidraw_fb_copy))
|
||||
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, config.datm, config.drawarea);
|
||||
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
{
|
||||
@@ -2696,8 +2687,13 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
IASetPrimitiveTopology(topology);
|
||||
|
||||
// Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy.
|
||||
ID3D11DepthStencilView* read_only_dsv = nullptr;
|
||||
if (config.tex && config.tex == config.ds)
|
||||
read_only_dsv = static_cast<GSTexture11*>(config.ds)->ReadOnlyDepthStencilView();
|
||||
|
||||
// Should be called before changing local srv state.
|
||||
PSUnbindConflictingSRVs(colclip_rt ? colclip_rt : config.rt, config.ds);
|
||||
PSUnbindConflictingSRVs(colclip_rt ? colclip_rt : config.rt, read_only_dsv ? nullptr : config.ds);
|
||||
|
||||
if (config.tex)
|
||||
{
|
||||
@@ -2713,11 +2709,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
SetupVS(config.vs, &config.cb_vs);
|
||||
SetupPS(config.ps, &config.cb_ps, config.sampler);
|
||||
|
||||
// Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy.
|
||||
ID3D11DepthStencilView* read_only_dsv = nullptr;
|
||||
if (config.tex && config.tex == config.ds)
|
||||
read_only_dsv = static_cast<GSTexture11*>(config.ds)->ReadOnlyDepthStencilView();
|
||||
|
||||
if (primid_texture)
|
||||
{
|
||||
OMDepthStencilSelector dss = config.depth;
|
||||
@@ -2765,6 +2756,11 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
|
||||
OMSetRenderTargets(draw_rt, draw_ds, &config.scissor, read_only_dsv);
|
||||
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant);
|
||||
|
||||
// Clear stencil as close as possible to the RT bind, to avoid framebuffer swaps.
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && multidraw_fb_copy)
|
||||
m_ctx->ClearDepthStencilView(*static_cast<GSTexture11*>(draw_ds), D3D11_CLEAR_STENCIL, 0.0f, 1);
|
||||
|
||||
SendHWDraw(config, draw_rt_clone, draw_rt, config.require_one_barrier, config.require_full_barrier, false);
|
||||
|
||||
if (config.blend_multi_pass.enable)
|
||||
|
||||
@@ -253,6 +253,10 @@ private:
|
||||
D3D11ShaderCache m_shader_cache;
|
||||
std::string m_tfx_source;
|
||||
|
||||
protected:
|
||||
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
|
||||
|
||||
public:
|
||||
GSDevice11();
|
||||
~GSDevice11() override;
|
||||
@@ -298,10 +302,8 @@ public:
|
||||
|
||||
void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override;
|
||||
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override;
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, bool linear = true);
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY) override;
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear = true);
|
||||
void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, bool linear);
|
||||
void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ID3D11PixelShader* ps, ID3D11Buffer* ps_cb, ID3D11BlendState* bs, bool linear);
|
||||
void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override;
|
||||
void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override;
|
||||
void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override;
|
||||
@@ -309,7 +311,7 @@ public:
|
||||
void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) override;
|
||||
void DoMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, const GSVector2& ds);
|
||||
|
||||
void SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, SetDATM datm);
|
||||
void SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox);
|
||||
|
||||
void* IAMapVertexBuffer(u32 stride, u32 count);
|
||||
void IAUnmapVertexBuffer(u32 stride, u32 count);
|
||||
|
||||
@@ -1425,30 +1425,17 @@ void GSDevice12::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r,
|
||||
dTex12->SetState(GSTexture::State::Dirty);
|
||||
}
|
||||
|
||||
void GSDevice12::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
ShaderConvert shader /* = ShaderConvert::COPY */, bool linear /* = true */)
|
||||
void GSDevice12::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear)
|
||||
{
|
||||
pxAssert(HasDepthOutput(shader) == (dTex && dTex->GetType() == GSTexture::Type::DepthStencil));
|
||||
|
||||
GL_INS("StretchRect(%d) {%d,%d} %dx%d -> {%d,%d) %dx%d", shader, int(sRect.left), int(sRect.top),
|
||||
int(sRect.right - sRect.left), int(sRect.bottom - sRect.top), int(dRect.left), int(dRect.top),
|
||||
int(dRect.right - dRect.left), int(dRect.bottom - dRect.top));
|
||||
|
||||
const bool allow_discard = (cms.wrgba == 0xf);
|
||||
const ID3D12PipelineState* state;
|
||||
if (HasVariableWriteMask(shader))
|
||||
state = m_color_copy[GetShaderIndexForMask(shader, cms.wrgba)].get();
|
||||
else
|
||||
state = dTex ? m_convert[static_cast<int>(shader)].get() : m_present[static_cast<int>(shader)].get();
|
||||
DoStretchRect(static_cast<GSTexture12*>(sTex), sRect, static_cast<GSTexture12*>(dTex), dRect,
|
||||
dTex ? m_convert[static_cast<int>(shader)].get() : m_present[static_cast<int>(shader)].get(), linear,
|
||||
ShaderConvertWriteMask(shader) == 0xf);
|
||||
}
|
||||
|
||||
void GSDevice12::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red,
|
||||
bool green, bool blue, bool alpha, ShaderConvert shader)
|
||||
{
|
||||
GL_PUSH("ColorCopy Red:%d Green:%d Blue:%d Alpha:%d", red, green, blue, alpha);
|
||||
|
||||
const u32 index = (red ? 1 : 0) | (green ? 2 : 0) | (blue ? 4 : 0) | (alpha ? 8 : 0);
|
||||
int rta_offset = (shader == ShaderConvert::RTA_CORRECTION) ? 16 : 0;
|
||||
const bool allow_discard = (index == 0xf);
|
||||
DoStretchRect(static_cast<GSTexture12*>(sTex), sRect, static_cast<GSTexture12*>(dTex), dRect,
|
||||
m_color_copy[index + rta_offset].get(), false, allow_discard);
|
||||
state, linear, allow_discard);
|
||||
}
|
||||
|
||||
void GSDevice12::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
@@ -1512,14 +1499,15 @@ void GSDevice12::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
|
||||
struct Uniforms
|
||||
{
|
||||
float weight;
|
||||
float pad0[3];
|
||||
float step_multiplier;
|
||||
float pad0[2];
|
||||
GSVector2i clamp_min;
|
||||
int downsample_factor;
|
||||
int pad1;
|
||||
};
|
||||
|
||||
const Uniforms cb = {
|
||||
static_cast<float>(downsample_factor * downsample_factor), {}, clamp_min, static_cast<int>(downsample_factor), 0};
|
||||
static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast<int>(downsample_factor), 0};
|
||||
SetUtilityRootSignature();
|
||||
SetUtilityPushConstants(&cb, sizeof(cb));
|
||||
|
||||
@@ -1638,10 +1626,10 @@ void GSDevice12::DoMultiStretchRects(
|
||||
SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
|
||||
SetUtilityTexture(rects[0].src, rects[0].linear ? m_linear_sampler_cpu : m_point_sampler_cpu);
|
||||
|
||||
pxAssert(shader == ShaderConvert::COPY || shader == ShaderConvert::RTA_CORRECTION || rects[0].wmask.wrgba == 0xf);
|
||||
int rta_bit = (shader == ShaderConvert::RTA_CORRECTION) ? 16 : 0;
|
||||
SetPipeline((rects[0].wmask.wrgba != 0xf) ? m_color_copy[rects[0].wmask.wrgba | rta_bit].get() :
|
||||
m_convert[static_cast<int>(shader)].get());
|
||||
pxAssert(HasVariableWriteMask(shader) || rects[0].wmask.wrgba == 0xf);
|
||||
SetPipeline((rects[0].wmask.wrgba != 0xf) ?
|
||||
m_color_copy[GetShaderIndexForMask(shader, rects[0].wmask.wrgba)].get() :
|
||||
m_convert[static_cast<int>(shader)].get());
|
||||
|
||||
if (ApplyUtilityState())
|
||||
DrawIndexedPrimitive();
|
||||
@@ -3830,8 +3818,12 @@ GSTexture12* GSDevice12::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, Pipe
|
||||
void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
{
|
||||
// Destination Alpha Setup
|
||||
const bool stencil_DATE = (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil ||
|
||||
config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne);
|
||||
const bool stencil_DATE_One = config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne;
|
||||
const bool stencil_DATE = (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil || stencil_DATE_One);
|
||||
|
||||
// TODO: Backport from vk.
|
||||
if (stencil_DATE_One)
|
||||
config.ps.date = 0;
|
||||
|
||||
GSTexture12* colclip_rt = static_cast<GSTexture12*>(g_gs_device->GetColorClipTexture());
|
||||
GSTexture12* draw_rt = static_cast<GSTexture12*>(config.rt);
|
||||
@@ -3939,9 +3931,6 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
{
|
||||
Console.Warning("D3D12: Failed to allocate ColorClip render target, aborting draw.");
|
||||
|
||||
if (draw_rt_clone)
|
||||
Recycle(draw_rt_clone);
|
||||
|
||||
if (date_image)
|
||||
Recycle(date_image);
|
||||
|
||||
@@ -3963,15 +3952,16 @@ 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)
|
||||
if (config.require_one_barrier && !m_features.multidraw_fb_copy)
|
||||
PSSetShaderResource(2, draw_rt, true);
|
||||
}
|
||||
|
||||
draw_rt = colclip_rt;
|
||||
}
|
||||
|
||||
// Clear texture binding when it's bound to RT, DSV will be read only.
|
||||
if (draw_rt && static_cast<GSTexture12*>(draw_rt)->GetSRVDescriptor() == m_tfx_textures[0])
|
||||
// Clear texture binding when it's bound to RT or DS.
|
||||
if (!config.tex && ((draw_rt && static_cast<GSTexture12*>(draw_rt)->GetSRVDescriptor() == m_tfx_textures[0]) ||
|
||||
(draw_ds && static_cast<GSTexture12*>(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0])))
|
||||
PSSetShaderResource(0, nullptr, false);
|
||||
|
||||
if (m_in_render_pass && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
|
||||
|
||||
@@ -390,6 +390,10 @@ private:
|
||||
|
||||
void DestroyResources();
|
||||
|
||||
protected:
|
||||
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
|
||||
|
||||
public:
|
||||
GSDevice12();
|
||||
~GSDevice12() override;
|
||||
@@ -428,10 +432,6 @@ public:
|
||||
|
||||
void CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r, u32 destX, u32 destY) override;
|
||||
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override;
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red,
|
||||
bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY) override;
|
||||
void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
PresentShader shader, float shaderTime, bool linear) override;
|
||||
void UpdateCLUTTexture(
|
||||
|
||||
@@ -37,6 +37,90 @@ static bool s_nativeres;
|
||||
// Partial level, broken on all renderers.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
const int get_next_ctx = r.m_env.PRIM.CTXT;
|
||||
const GSDrawingContext& next_ctx = r.m_env.CTXT[get_next_ctx];
|
||||
|
||||
// Game does alternate line channel shuffles with blending, we can't handle this and the first one does it, so skip the second.
|
||||
if (RTME && RTPSM == PSMT8 && (RTBP0 + 0x20) == next_ctx.TEX0.TBP0 && RFBP == next_ctx.FRAME.Block())
|
||||
{
|
||||
skip = 1;
|
||||
return false;
|
||||
}
|
||||
// Detect the deswizzling shuffle from depth, copying the RG and BA separately on each half of the page (ignore the split).
|
||||
if (RTME && RFBP != RTBP0 && RFPSM == PSMCT16S && RTPSM == PSMCT16S)
|
||||
{
|
||||
if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index.tail == 128)
|
||||
{
|
||||
const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y/2, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y/2);
|
||||
const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y/2, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y/2);
|
||||
r.m_cached_ctx.TEX0.PSM = PSMCT32;
|
||||
r.m_cached_ctx.FRAME.PSM = PSMCT32;
|
||||
r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Following the previous draw, it tries to copy everything read from depth and offset it by 2, for the alternate line channel shuffle (skipped above).
|
||||
if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index.tail == 2)
|
||||
{
|
||||
GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y - 2.0f, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y - 2.0f);
|
||||
GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y);
|
||||
r.ReplaceVerticesWithSprite(draw_size, read_size, GSVector2i(read_size.width(), read_size.height()), draw_size);
|
||||
|
||||
|
||||
// Fix up the shuffle from last draw.
|
||||
{
|
||||
GIFRegTEX0 RTLookup = GIFRegTEX0::Create(RTBP0, RFBW, RFPSM);
|
||||
GSTextureCache::Source* src = g_texture_cache->LookupSource(true, RTLookup, r.m_cached_ctx.TEXA, r.m_cached_ctx.CLAMP, GSVector4i(0,0,1,1), nullptr, true, false, r.m_cached_ctx.FRAME, true, true);
|
||||
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i(0,0,1,1), true, false, true, src);
|
||||
|
||||
if (!rt)
|
||||
return false;
|
||||
|
||||
GSLocalMemory::psm_t rt_psm = GSLocalMemory::m_psm[RFPSM];
|
||||
int page_offset = (RTBP0 - rt->m_TEX0.TBP0) >> 5;
|
||||
int vertical_offset = page_offset / std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.y;
|
||||
int horizontal_offset = page_offset % std::max(rt->m_TEX0.TBW, 1U) * rt_psm.pgs.x;
|
||||
|
||||
draw_size = draw_size + GSVector4i(horizontal_offset, vertical_offset, horizontal_offset, vertical_offset);
|
||||
rt->UnscaleRTAlpha();
|
||||
|
||||
// Shuffle the blue channel in to red, leave green as-is.
|
||||
GSHWDrawConfig& config = r.BeginHLEHardwareDraw(
|
||||
rt->GetTexture(), nullptr, rt->GetScale(), rt->GetTexture(), rt->GetScale(), draw_size);
|
||||
config.ps.shuffle = 1;
|
||||
config.ps.dst_fmt = GSLocalMemory::PSM_FMT_32;
|
||||
config.ps.write_rg = 0;
|
||||
config.ps.shuffle_same = 0;
|
||||
config.ps.real16src = 0;
|
||||
config.ps.shuffle_across = 1;
|
||||
config.ps.process_rg = r.SHUFFLE_WRITE;
|
||||
config.ps.process_ba = r.SHUFFLE_READ;
|
||||
config.colormask.wrgba = 0;
|
||||
config.colormask.wr = 1;
|
||||
config.ps.rta_correction = 0;
|
||||
config.ps.rta_source_correction = 0;
|
||||
config.ps.tfx = TFX_DECAL;
|
||||
config.ps.tcc = true;
|
||||
r.EndHLEHardwareDraw(true);
|
||||
|
||||
rt->m_alpha_min = 0;
|
||||
rt->m_alpha_max = 255;
|
||||
|
||||
rt = nullptr;
|
||||
src = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Channel effect not properly supported yet
|
||||
bool GSHwHack::GSC_Manhunt2(GSRendererHW& r, int& skip)
|
||||
{
|
||||
@@ -467,55 +551,6 @@ bool GSHwHack::GSC_TalesOfLegendia(GSRendererHW& r, int& skip)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSHwHack::GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, int& skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
if (RTME && RTPSM == PSMCT16S && (RFBMSK >= 0x6FFFFFFF || RFBMSK == 0))
|
||||
{
|
||||
skip = 1000;
|
||||
}
|
||||
else if (RTME && RTPSM == PSMCT32 && RFBMSK == 0xFF000000)
|
||||
{
|
||||
skip = 2; // Fog
|
||||
}
|
||||
else if ((RFBP | RTBP0) && RFPSM == RTPSM && RTPSM == PSMCT16 && RFBMSK == 0x3FFF)
|
||||
{
|
||||
// Note start of the effect (texture shuffle) is fixed but maybe not the extra draw call
|
||||
skip = 1000;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!RTME && RTPSM == PSMCT32 && RFBP == 0x1180 && RTBP0 == 0x1180 && (RFBMSK == 0))
|
||||
{
|
||||
skip = 0;
|
||||
}
|
||||
if (RTME && RTPSM == PSMT4 && RFBP && (RTBP0 != 0x3753))
|
||||
{
|
||||
skip = 0;
|
||||
}
|
||||
if (RTME && RTPSM == PSMT8H && RFBP == 0x22e0 && RTBP0 == 0x36e0)
|
||||
{
|
||||
skip = 0;
|
||||
}
|
||||
if (!RTME && RTPSM == PSMT8H && RFBP == 0x22e0)
|
||||
{
|
||||
skip = 0;
|
||||
}
|
||||
if (RTME && RTPSM == PSMT8 && (RFBP == 0x1180 || RFBP == 0) && (RTBP0 != 0x3764 && RTBP0 != 0x370f))
|
||||
{
|
||||
skip = 0;
|
||||
}
|
||||
if (RTME && RTPSM == PSMCT16S && (RFBP == 0x1180))
|
||||
{
|
||||
skip = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSHwHack::GSC_UltramanFightingEvolution(GSRendererHW& r, int& skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
@@ -567,27 +602,6 @@ bool GSHwHack::GSC_UrbanReign(GSRendererHW& r, int& skip)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSHwHack::GSC_SteambotChronicles(GSRendererHW& r, int& skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
// Author: miseru99 on forums.pcsx2.net
|
||||
if (RTME && RTPSM == PSMCT16S)
|
||||
{
|
||||
if (RFBP == 0x1180)
|
||||
{
|
||||
skip = 1; // 1 deletes some of the glitched effects
|
||||
}
|
||||
else if (RFBP == 0)
|
||||
{
|
||||
skip = 100; // deletes most others(too high deletes the buggy sea completely;c, too low causes glitches to be visible)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSHwHack::GSC_NFSUndercover(GSRendererHW& r, int& skip)
|
||||
{
|
||||
// NFS Undercover does a weird texture shuffle by page, which really isn't supported by our TC.
|
||||
@@ -1382,6 +1396,7 @@ bool GSHwHack::MV_Ico(GSRendererHW& r)
|
||||
#define CRC_F(name) { #name, &GSHwHack::name }
|
||||
|
||||
const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_get_skip_count_functions[] = {
|
||||
CRC_F(GSC_IRem),
|
||||
CRC_F(GSC_Manhunt2),
|
||||
CRC_F(GSC_MidnightClub3),
|
||||
CRC_F(GSC_SacredBlaze),
|
||||
@@ -1391,7 +1406,6 @@ const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_get_skip_count_function
|
||||
CRC_F(GSC_TalesOfLegendia),
|
||||
CRC_F(GSC_TalesofSymphonia),
|
||||
CRC_F(GSC_UrbanReign),
|
||||
CRC_F(GSC_ZettaiZetsumeiToshi2),
|
||||
CRC_F(GSC_BlackAndBurnoutSky),
|
||||
CRC_F(GSC_BlueTongueGames),
|
||||
CRC_F(GSC_NFSUndercover),
|
||||
@@ -1403,7 +1417,6 @@ const GSHwHack::Entry<GSRendererHW::GSC_Ptr> GSHwHack::s_get_skip_count_function
|
||||
// Channel Effect
|
||||
CRC_F(GSC_NamcoGames),
|
||||
CRC_F(GSC_SandGrainGames),
|
||||
CRC_F(GSC_SteambotChronicles),
|
||||
|
||||
// Depth Issue
|
||||
CRC_F(GSC_BurnoutGames),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class GSHwHack
|
||||
{
|
||||
public:
|
||||
static bool GSC_IRem(GSRendererHW& r, int& skip);
|
||||
static bool GSC_Manhunt2(GSRendererHW& r, int& skip);
|
||||
static bool GSC_SacredBlaze(GSRendererHW& r, int& skip);
|
||||
static bool GSC_GuitarHero(GSRendererHW& r, int& skip);
|
||||
@@ -17,11 +18,9 @@ public:
|
||||
static bool GSC_BlackAndBurnoutSky(GSRendererHW& r, int& skip);
|
||||
static bool GSC_MidnightClub3(GSRendererHW& r, int& skip);
|
||||
static bool GSC_TalesOfLegendia(GSRendererHW& r, int& skip);
|
||||
static bool GSC_ZettaiZetsumeiToshi2(GSRendererHW& r, int& skip);
|
||||
static bool GSC_UltramanFightingEvolution(GSRendererHW& r, int& skip);
|
||||
static bool GSC_TalesofSymphonia(GSRendererHW& r, int& skip);
|
||||
static bool GSC_UrbanReign(GSRendererHW& r, int& skip);
|
||||
static bool GSC_SteambotChronicles(GSRendererHW& r, int& skip);
|
||||
static bool GSC_BlueTongueGames(GSRendererHW& r, int& skip);
|
||||
static bool GSC_NFSUndercover(GSRendererHW& r, int& skip);
|
||||
static bool GSC_Battlefield2(GSRendererHW& r, int& skip);
|
||||
|
||||
@@ -26,7 +26,7 @@ GSRendererHW::GSRendererHW()
|
||||
// Hope nothing requires too many draw calls.
|
||||
m_drawlist.reserve(2048);
|
||||
|
||||
memset(&m_conf, 0, sizeof(m_conf));
|
||||
memset(static_cast<void*>(&m_conf), 0, sizeof(m_conf));
|
||||
|
||||
ResetStates();
|
||||
}
|
||||
@@ -1760,7 +1760,7 @@ bool GSRendererHW::IsUsingAsInBlend()
|
||||
}
|
||||
bool GSRendererHW::ChannelsSharedTEX0FRAME()
|
||||
{
|
||||
if (!IsRTWritten() && !m_cached_ctx.TEST.DATE)
|
||||
if (!m_cached_ctx.TEST.DATE && !IsRTWritten())
|
||||
return false;
|
||||
|
||||
return GSUtil::GetChannelMask(m_cached_ctx.FRAME.PSM, m_cached_ctx.FRAME.FBMSK) & GSUtil::GetChannelMask(m_cached_ctx.TEX0.PSM);
|
||||
@@ -1778,7 +1778,7 @@ bool GSRendererHW::IsTBPFrameOrZ(u32 tbp, bool frame_only)
|
||||
const u32 fm_mask = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk;
|
||||
|
||||
const u32 max_z = (0xFFFFFFFF >> (GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].fmt * 8));
|
||||
const bool no_rt = (!IsRTWritten() && !m_cached_ctx.TEST.DATE);
|
||||
const bool no_rt = (!m_cached_ctx.TEST.DATE && !IsRTWritten());
|
||||
const bool no_ds = (
|
||||
// Depth is always pass/fail (no read) and write are discarded.
|
||||
(zm != 0 && m_cached_ctx.TEST.ZTST <= ZTST_ALWAYS) ||
|
||||
@@ -2517,7 +2517,7 @@ void GSRendererHW::Draw()
|
||||
// 2/ SuperMan really draws (0,0,0,0) color and a (0) 32-bits depth
|
||||
// 3/ 50cents really draws (0,0,0,128) color and a (0) 24 bits depth
|
||||
// Note: FF DoC has both buffer at same location but disable the depth test (write?) with ZTE = 0
|
||||
bool no_rt = (!IsRTWritten() && !m_cached_ctx.TEST.DATE);
|
||||
bool no_rt = (!m_cached_ctx.TEST.DATE && !IsRTWritten());
|
||||
const bool all_depth_tests_pass = IsDepthAlwaysPassing();
|
||||
bool no_ds = (zm != 0 && all_depth_tests_pass) ||
|
||||
// No color or Z being written.
|
||||
@@ -3151,18 +3151,28 @@ void GSRendererHW::Draw()
|
||||
m_cached_ctx.ZBUF.ZMSK = (new_zm != 0);
|
||||
fm = new_fm;
|
||||
zm = new_zm;
|
||||
no_rt = no_rt || (!IsRTWritten() && !m_cached_ctx.TEST.DATE);
|
||||
no_rt = no_rt || (!m_cached_ctx.TEST.DATE && !IsRTWritten());
|
||||
no_ds = no_ds || (zm != 0 && all_depth_tests_pass) ||
|
||||
// Depth will be written through the RT
|
||||
(!no_rt && m_cached_ctx.FRAME.FBP == m_cached_ctx.ZBUF.ZBP && !PRIM->TME && zm == 0 && (fm & fm_mask) == 0 && m_cached_ctx.TEST.ZTE) ||
|
||||
// No color or Z being written.
|
||||
(no_rt && zm != 0);
|
||||
if (no_rt && no_ds)
|
||||
{
|
||||
GL_INS("HW: Late draw cancel because no pixels pass alpha test.");
|
||||
CleanupDraw(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
no_rt = no_rt || (!m_cached_ctx.TEST.DATE && !IsRTWritten());
|
||||
no_ds = no_ds ||
|
||||
// Depth will be written through the RT
|
||||
(!no_rt && m_cached_ctx.FRAME.FBP == m_cached_ctx.ZBUF.ZBP && !PRIM->TME && zm == 0 && (fm & fm_mask) == 0 && m_cached_ctx.TEST.ZTE) ||
|
||||
// No color or Z being written.
|
||||
(no_rt && zm != 0);
|
||||
}
|
||||
|
||||
if (no_rt && no_ds)
|
||||
{
|
||||
GL_INS("HW: Late draw cancel.");
|
||||
CleanupDraw(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3192,11 +3202,12 @@ void GSRendererHW::Draw()
|
||||
// 2 == Upscale, so likely putting it over the top of the render target.
|
||||
if (scale_draw == 1)
|
||||
{
|
||||
target_scale = 1.0f;
|
||||
if (!PRIM->ABE || GSConfig.UserHacks_NativeScaling < GSNativeScaling::NormalUpscaled)
|
||||
target_scale = 1.0f;
|
||||
m_downscale_source = src->m_from_target ? src->m_from_target->GetScale() > 1.0f : false;
|
||||
}
|
||||
else
|
||||
m_downscale_source = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive || !src->m_from_target) ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
|
||||
m_downscale_source = ((GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive && GSConfig.UserHacks_NativeScaling != GSNativeScaling::AggressiveUpscaled) || !src->m_from_target) ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3516,7 +3527,7 @@ void GSRendererHW::Draw()
|
||||
// Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed.
|
||||
const bool preserve_downscale_draw = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && (std::abs(scale_draw) == 1 || (scale_draw == 0 && src && src->m_from_target && src->m_from_target->m_downscaled))) || is_possible_mem_clear == ClearType::ClearWithDraw;
|
||||
|
||||
rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true,
|
||||
rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && (GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal || GSConfig.UserHacks_NativeScaling == GSNativeScaling::NormalUpscaled) && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true,
|
||||
fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(),
|
||||
GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, ds, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0));
|
||||
|
||||
@@ -5456,7 +5467,9 @@ __ri bool GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool
|
||||
// Adjust it back to the page boundary
|
||||
min_uv.x -= block_offset.x * t_psm.bs.x;
|
||||
min_uv.y -= block_offset.y * t_psm.bs.y;
|
||||
|
||||
// Mask the channel.
|
||||
min_uv.y &= 2;
|
||||
min_uv.x &= 8;
|
||||
//if (/*GSLocalMemory::IsPageAligned(src->m_TEX0.PSM, m_r) &&*/
|
||||
// block_offset.eq(m_r_block_offset))
|
||||
{
|
||||
@@ -5747,8 +5760,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
|
||||
// Replace Ad with As, blend flags will be used from As since we are chaging the blend_index value.
|
||||
// Must be done before index calculation, after blending equation optimizations
|
||||
const bool blend_ad = m_conf.ps.blend_c == 1;
|
||||
const bool alpha_mask = (m_cached_ctx.FRAME.FBMSK & 0xFF000000) == 0xFF000000;
|
||||
bool blend_ad_alpha_masked = blend_ad && alpha_mask;
|
||||
bool blend_ad_alpha_masked = blend_ad && !m_conf.colormask.wa;
|
||||
const bool is_basic_blend = GSConfig.AccurateBlendingUnit != AccBlendLevel::Minimum;
|
||||
if (blend_ad_alpha_masked && (((is_basic_blend || (COLCLAMP.CLAMP == 0)) && (features.texture_barrier || features.multidraw_fb_copy))
|
||||
|| ((GSConfig.AccurateBlendingUnit >= AccBlendLevel::Medium) || m_conf.require_one_barrier)))
|
||||
@@ -6714,7 +6726,9 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
|
||||
// Bigger problem when WH is 1024x1024 and the target is only small.
|
||||
// This "fixes" a lot of the rainbow garbage in games when upscaling (and xenosaga shadows + VP2 forest seem quite happy).
|
||||
// Note that this is done on the original texture scale, during upscales it can mess up otherwise.
|
||||
const GSVector4 region_clamp_offset = GSVector4::cxpr(0.5f, 0.5f, 0.1f, 0.1f) + (GSVector4::cxpr(0.1f, 0.1f, 0.0f, 0.0f) * tex->GetScale());
|
||||
const GSVector4 region_clamp_offset = ((GSConfig.UserHacks_HalfPixelOffset == GSHalfPixelOffset::Native && tex->GetScale() > 1.0f) && !m_channel_shuffle) ?
|
||||
(GSVector4::cxpr(1.0f, 1.0f, 0.1f, 0.1f) + (GSVector4::cxpr(0.1f, 0.1f, 0.0f, 0.0f) * tex->GetScale())) :
|
||||
GSVector4::cxpr(0.5f, 0.5f, 0.1f, 0.1f);
|
||||
|
||||
const GSVector4 region_clamp = (GSVector4(clamp) + region_clamp_offset) / WH.xyxy();
|
||||
if (wms >= CLAMP_REGION_CLAMP)
|
||||
@@ -7234,7 +7248,7 @@ void GSRendererHW::ResetStates()
|
||||
{
|
||||
// We don't want to zero out the constant buffers, since fields used by the current draw could result in redundant uploads.
|
||||
// This memset should be pretty efficient - the struct is 16 byte aligned, as is the cb_vs offset.
|
||||
memset(&m_conf, 0, reinterpret_cast<const char*>(&m_conf.cb_vs) - reinterpret_cast<const char*>(&m_conf));
|
||||
memset(static_cast<void*>(&m_conf), 0, reinterpret_cast<const char*>(&m_conf.cb_vs) - reinterpret_cast<const char*>(&m_conf));
|
||||
}
|
||||
|
||||
__ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm)
|
||||
@@ -7649,6 +7663,21 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
if ((!IsOpaque() || m_context->ALPHA.IsBlack()) && rt && ((m_conf.colormask.wrgba & 0x7) || (m_texture_shuffle && !m_copy_16bit_to_target_shuffle && !m_same_group_texture_shuffle)))
|
||||
{
|
||||
EmulateBlending(blend_alpha_min, blend_alpha_max, DATE, DATE_PRIMID, DATE_BARRIER, rt, can_scale_rt_alpha, new_scale_rt_alpha);
|
||||
|
||||
// Similar to IsRTWritten(), check if the rt will change.
|
||||
const bool no_rt = (!DATE && !m_conf.colormask.wrgba && !m_channel_shuffle);
|
||||
const bool no_ds = !m_conf.ds ||
|
||||
// Depth will be written through the RT.
|
||||
(!no_rt && m_cached_ctx.FRAME.FBP == m_cached_ctx.ZBUF.ZBP && !PRIM->TME && m_cached_ctx.ZBUF.ZMSK == 0 &&
|
||||
(m_cached_ctx.FRAME.FBMSK & GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk) == 0 && m_cached_ctx.TEST.ZTE) ||
|
||||
// No color or Z being written.
|
||||
(no_rt && m_cached_ctx.ZBUF.ZMSK != 0);
|
||||
|
||||
if (no_rt && no_ds)
|
||||
{
|
||||
GL_INS("HW: Late draw cancel EmulateBlending().");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -7666,6 +7695,11 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
}
|
||||
}
|
||||
|
||||
// Always swap DATE with DATE_BARRIER if we have barriers on when alpha write is masked.
|
||||
// This is always enabled on vk/gl but not on dx11/12 as copies are slow so we can selectively enable it like now.
|
||||
if (DATE && !m_conf.colormask.wa && (m_conf.require_one_barrier || m_conf.require_full_barrier))
|
||||
DATE_BARRIER = true;
|
||||
|
||||
if ((m_conf.ps.tex_is_fb && rt && rt->m_rt_alpha_scale) || (tex && tex->m_from_target && tex->m_target_direct && tex->m_from_target->m_rt_alpha_scale))
|
||||
m_conf.ps.rta_source_correction = 1;
|
||||
|
||||
@@ -7905,7 +7939,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
}
|
||||
else if (DATE_one)
|
||||
{
|
||||
if (features.texture_barrier)
|
||||
const bool multidraw_fb_copy = features.multidraw_fb_copy && (m_conf.require_one_barrier || m_conf.require_full_barrier);
|
||||
if (features.texture_barrier || multidraw_fb_copy)
|
||||
{
|
||||
m_conf.require_one_barrier = true;
|
||||
m_conf.ps.date = 5 + m_cached_ctx.TEST.DATM;
|
||||
@@ -9398,8 +9433,8 @@ GSHWDrawConfig& GSRendererHW::BeginHLEHardwareDraw(
|
||||
|
||||
// Bit gross, but really no other way to ensure there's nothing of the last draw left over.
|
||||
GSHWDrawConfig& config = m_conf;
|
||||
std::memset(&config.cb_vs, 0, sizeof(config.cb_vs));
|
||||
std::memset(&config.cb_ps, 0, sizeof(config.cb_ps));
|
||||
std::memset(static_cast<void*>(&config.cb_vs), 0, sizeof(config.cb_vs));
|
||||
std::memset(static_cast<void*>(&config.cb_ps), 0, sizeof(config.cb_ps));
|
||||
|
||||
// Reused between draws, since the draw config is shared, you can't have multiple draws in flight anyway.
|
||||
static GSVertex vertices[4];
|
||||
@@ -9532,4 +9567,4 @@ std::size_t GSRendererHW::ComputeDrawlistGetSize(float scale)
|
||||
GetPrimitiveOverlapDrawlist(true, save_bbox, scale);
|
||||
}
|
||||
return m_drawlist.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1347,6 +1347,11 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
int x_offset = 0;
|
||||
int y_offset = 0;
|
||||
|
||||
// Indicates that in looking for targets that match the source BP, we found a perfect BP match
|
||||
// but the target's valid area was outside the required area for the source. In such cases
|
||||
// we want to create a temporary source and load the data from memory.
|
||||
bool target_bp_hit_outside_valid_area = false;
|
||||
|
||||
#ifdef DISABLE_HW_TEXTURE_CACHE
|
||||
if (0)
|
||||
#else
|
||||
@@ -1723,8 +1728,17 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
{
|
||||
const bool outside_target = !t->Overlaps(bp, bw, psm, r);
|
||||
|
||||
if (!possible_shuffle && TEX0.PSM == PSMT8 && outside_target)
|
||||
if (!possible_shuffle && outside_target)
|
||||
{
|
||||
// Hit a target but source required area outside the target's valid area.
|
||||
target_bp_hit_outside_valid_area = true;
|
||||
GL_CACHE(
|
||||
"TC: LookupSource: Target BP match but outside valid area;"
|
||||
" Source=(BP=%04x, BW=%d, PSM=%s, req=(%x,%x - %x,%x));"
|
||||
" Target=(BP=%04x, BW=%d, PSM=%s, valid_area=(%x,%x - %x,%x))",
|
||||
bp, bw, GSUtil::GetPSMName(psm), r.x, r.y, r.z, r.w,
|
||||
t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM),
|
||||
t->m_valid.x, t->m_valid.y, t->m_valid.z, t->m_valid.w);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -1761,6 +1775,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
if (GSLocalMemory::m_psm[psm].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && bw != rt_tbw && block_boundary_rect.height() > GSLocalMemory::m_psm[psm].pgs.y)
|
||||
continue;
|
||||
|
||||
// Reading 16bit as 32bit, or vice versa (when there isn't a shuffle) isn't really possible and no conversion is done.
|
||||
if (!possible_shuffle && std::abs(GSLocalMemory::m_psm[psm].bpp - GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp) == 16)
|
||||
continue;
|
||||
|
||||
if (GSLocalMemory::m_psm[color_psm].bpp == 16 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 32 && bw != 1 &&
|
||||
((t->m_TEX0.TBW < (horz_page_offset + ((block_boundary_rect.z + GSLocalMemory::m_psm[psm].pgs.x - 1) / GSLocalMemory::m_psm[psm].pgs.x)) ||
|
||||
(t->m_TEX0.TBW != bw && block_boundary_rect.w > GSLocalMemory::m_psm[psm].pgs.y))))
|
||||
@@ -1823,6 +1841,10 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure it's inside if not a shuffle, sometimes valid areas can get messy, like TOCA Race Driver 2 where it goes over to 480, but it's rounded up to 512 in the shuffle.
|
||||
if (!possible_shuffle && !t->Inside(bp, bw, psm, block_boundary_rect))
|
||||
continue;
|
||||
|
||||
GSVector4i new_rect = (GSLocalMemory::m_psm[color_psm].bpp != GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp && (psm & 0x7) != PSMCT16) ? block_boundary_rect : rect;
|
||||
|
||||
// If the sizing is completely wrong on the frame vs the source when reading from alpha then it's likely the target has 2 different sizes for rgb and alpha.
|
||||
@@ -2026,7 +2048,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
//
|
||||
// Sigh... They don't help us.
|
||||
|
||||
if (!found_t && !dst && !GSConfig.UserHacks_DisableDepthSupport)
|
||||
if (!found_t && !dst && !GSConfig.UserHacks_DisableDepthSupport && !target_bp_hit_outside_valid_area)
|
||||
{
|
||||
// Let's try a trick to avoid to use wrongly a depth buffer
|
||||
// Unfortunately, I don't have any Arc the Lad testcase
|
||||
@@ -2164,7 +2186,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
}
|
||||
}
|
||||
|
||||
src = CreateSource(src_TEX0, TEXA, dst, x_offset, y_offset, lod, &rect, gpu_clut, region);
|
||||
src = CreateSource(src_TEX0, TEXA, dst, x_offset, y_offset, lod, &rect, gpu_clut, region, target_bp_hit_outside_valid_area && TEX0.PSM != PSMT8);
|
||||
if (!src) [[unlikely]]
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2473,7 +2495,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
|
||||
const bool ds_offset = !ds || offset != 0;
|
||||
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
|
||||
const bool source_match = src && src->m_TEX0.TBP0 == bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t;
|
||||
const bool source_match = src && src->m_TEX0.TBP0 <= bp && src->m_end_block > bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect);
|
||||
const bool was_used_last_draw = t->m_last_draw == (GSState::s_n - 1);
|
||||
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
|
||||
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
|
||||
@@ -2641,8 +2663,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
|
||||
for (auto i = list.begin(); i != list.end(); ++i)
|
||||
{
|
||||
Target* t = *i;
|
||||
const bool half_buffer_match = GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && TEX0.TBW == t->m_TEX0.TBW && TEX0.PSM == t->m_TEX0.PSM &&
|
||||
bp == GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, GSVector4i(0, size.y, size.x, size.y + 1));
|
||||
// Make sure the target is inside the texture
|
||||
if (t->m_TEX0.TBP0 <= bp && bp <= t->m_end_block && t->Inside(bp, TEX0.TBW, TEX0.PSM, GSVector4i::loadh(size)))
|
||||
if (t->m_TEX0.TBP0 <= bp && bp <= t->m_end_block && (half_buffer_match || t->Inside(bp, TEX0.TBW, TEX0.PSM, GSVector4i::loadh(size))))
|
||||
{
|
||||
if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))
|
||||
continue;
|
||||
@@ -3451,6 +3475,10 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
// When the write covers the entire target, don't bother checking any earlier writes.
|
||||
if (iter->blit.DBP <= TEX0.TBP0 && transfer_end >= rect_end)
|
||||
{
|
||||
// If it was a clear draw then we can use that as our target size.
|
||||
if (iter->zero_clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
|
||||
dst->UpdateValidity(iter->rect);
|
||||
|
||||
// Some games clear RT and Z at the same time, only erase if it's specifically this target.
|
||||
if (iter->blit.DBP == TEX0.TBP0 && transfer_end == rect_end)
|
||||
transfers.erase(iter.base() - 1);
|
||||
@@ -4748,9 +4776,6 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
{
|
||||
Target* t = *it;
|
||||
|
||||
if (t->m_32_bits_fmt && t->m_TEX0.PSM > PSMCT24)
|
||||
t->m_TEX0.PSM = PSMCT32;
|
||||
|
||||
const bool exact_bp = t->m_TEX0.TBP0 == bp;
|
||||
// pass 0 == Exact match, pass 1 == partial match
|
||||
if (pass == 0)
|
||||
@@ -5764,7 +5789,7 @@ void GSTextureCache::IncAge()
|
||||
}
|
||||
|
||||
//Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work.
|
||||
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region)
|
||||
GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* dst, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temp)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
Source* src = new Source(TEX0, TEXA);
|
||||
@@ -6230,7 +6255,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GSUtil::GetChannelMask(TEX0.PSM) == 0xf && TEX0.TBP0 != GSRendererHW::GetInstance()->GetCachedCtx()->FRAME.Block() && TEX0.TBP0 != GSRendererHW::GetInstance()->GetCachedCtx()->ZBUF.Block())
|
||||
if (GSUtil::GetChannelMask(TEX0.PSM) == 0xf && TEX0.TBP0 != GSRendererHW::GetInstance()->GetCachedCtx()->FRAME.Block() && TEX0.TBP0 != GSRendererHW::GetInstance()->GetCachedCtx()->ZBUF.Block() && !force_temp)
|
||||
{
|
||||
// Kill any possible targets we missed, they might be wrong now.
|
||||
g_texture_cache->InvalidateVideoMemType(GSTextureCache::RenderTarget, TEX0.TBP0, TEX0.PSM, GSRendererHW::GetInstance()->GetCachedCtx()->FRAME.FBMSK, true);
|
||||
@@ -6238,7 +6263,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
||||
}
|
||||
|
||||
// kill source immediately after the draw if it's the RT, because that'll get invalidated immediately.
|
||||
if (GSRendererHW::GetInstance()->IsTBPFrameOrZ(TEX0.TBP0, true) && GSRendererHW::GetInstance()->ChannelsSharedTEX0FRAME())
|
||||
if (force_temp || (GSRendererHW::GetInstance()->IsTBPFrameOrZ(TEX0.TBP0, true) && GSRendererHW::GetInstance()->ChannelsSharedTEX0FRAME()))
|
||||
{
|
||||
GL_CACHE("TC: Source == RT before RT creation, invalidating after draw.");
|
||||
m_temporary_source = src;
|
||||
@@ -6893,6 +6918,24 @@ GSTexture* GSTextureCache::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVec
|
||||
GL_INS("TC: Exact match on BP 0x%04x BW %u", t->m_TEX0.TBP0, t->m_TEX0.TBW);
|
||||
this_offset.x = 0;
|
||||
this_offset.y = 0;
|
||||
|
||||
// If we're using native scaling we can take this opertunity to downscale the target, it should maintain this.
|
||||
if (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && t->m_scale > 1.0f)
|
||||
{
|
||||
GSTexture* tex = t->m_type == RenderTarget ? g_gs_device->CreateRenderTarget(t->m_unscaled_size.x, t->m_unscaled_size.y, GSTexture::Format::Color, false) :
|
||||
g_gs_device->CreateDepthStencil(t->m_unscaled_size.x, t->m_unscaled_size.y, GSTexture::Format::DepthStencil, false);
|
||||
if (!tex)
|
||||
return nullptr;
|
||||
|
||||
g_gs_device->StretchRect(t->m_texture, GSVector4(0, 0, 1, 1), tex, GSVector4(GSVector4i::loadh(t->m_unscaled_size)), (t->m_type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, false);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
m_target_memory_usage = (m_target_memory_usage - t->m_texture->GetMemUsage()) + tex->GetMemUsage();
|
||||
g_gs_device->Recycle(t->m_texture);
|
||||
|
||||
t->m_texture = tex;
|
||||
t->m_scale = 1.0f;
|
||||
t->m_downscaled = true;
|
||||
}
|
||||
}
|
||||
else if (GSConfig.UserHacks_GPUTargetCLUTMode == GSGPUTargetCLUTMode::InsideTarget &&
|
||||
t->m_TEX0.TBP0 < CBP && t->m_end_block >= CBP)
|
||||
|
||||
@@ -445,7 +445,7 @@ protected:
|
||||
std::unique_ptr<GSDownloadTexture> m_uint16_download_texture;
|
||||
std::unique_ptr<GSDownloadTexture> m_uint32_download_texture;
|
||||
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region);
|
||||
Source* CreateSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, Target* t, int x_offset, int y_offset, const GSVector2i* lod, const GSVector4i* src_range, GSTexture* gpu_clut, SourceRegion region, bool force_temporary = false);
|
||||
|
||||
bool PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, bool is_frame,
|
||||
bool preload, bool preserve_target, const GSVector4i draw_rect, Target* dst, GSTextureCache::Source* src = nullptr);
|
||||
|
||||
@@ -407,8 +407,6 @@ public:
|
||||
void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2& ds);
|
||||
/// Copy from a position in sTex to the same position in the currently active render encoder using the given fs pipeline and rect
|
||||
void RenderCopy(GSTexture* sTex, id<MTLRenderPipelineState> pipeline, const GSVector4i& rect);
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override;
|
||||
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY) override;
|
||||
void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override;
|
||||
void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) override;
|
||||
void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override;
|
||||
@@ -452,6 +450,10 @@ public:
|
||||
|
||||
void RenderImGui(ImDrawData* data);
|
||||
u32 FrameNo() const { return m_frame; }
|
||||
|
||||
protected:
|
||||
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
|
||||
};
|
||||
|
||||
static constexpr bool IsCommandBufferCompleted(MTLCommandBufferStatus status)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user