Compare commits

...

126 Commits

Author SHA1 Message Date
TJnotJT
35624a12d9 GS/HW: Refactor StretchRect() to have single entry to renderer/reduce duplication. 2025-11-25 18:38:52 +01:00
JordanTheToaster
9b147cc57c GameDB: FFX-2 FMV FIxes 2025-11-25 17:50:06 +01:00
JordanTheToaster
10e13cfece GS/HW: Test double buffer offset from target base address on lookup 2025-11-25 17:50:06 +01:00
lightningterror
7b2eb7bc47 GS/DX11: Do Stencil date_one in a single pass if there's a barrier.
We currently have a barrier so let's do date_one in a single pass, plus this avoids any issues with copies and stencil issues.
On dx copies are slower so we can only use the optimization if we have barriers already present.
2025-11-25 16:58:27 +01:00
PCSX2 Bot
ab1cb802d8 [ci skip] Qt: Update Base Translation. 2025-11-25 01:01:42 +01:00
TheLastRar
366cdd8df0 Qt: Remove incorrect check in shouldMouseLock() 2025-11-24 18:34:43 -05:00
TheLastRar
bc3cfb1373 Qt: Improve mouse lock DPI handling 2025-11-24 18:34:43 -05:00
SternXD
db6792af2e Achievements/Qt: Show success feedback on RetroAchievements login 2025-11-24 18:19:08 -05:00
SternXD
a1485fb7cd FullscreenUI: Allow pause menu to wrap around 2025-11-24 18:14:44 -05:00
Ty
c72c309218 InputRecording: Use u32 for frames since that's what the core uses (warning fixes) 2025-11-24 18:07:25 -05:00
Ty
58899a9ed3 MTVU: uptr to u32 implicit cast fix 2025-11-24 18:07:25 -05:00
Ty
0823c70460 VTLB: uptr to u32 implicit cast warning fix 2025-11-24 18:07:25 -05:00
Ty
e5c29a3975 BiosTools: s64 to to u32 implicit cast warning fix 2025-11-24 18:07:25 -05:00
Ty
1174ae99c9 GS SW: size_t to int implicit cast warning fixes 2025-11-24 18:07:25 -05:00
Ty
e2d3680038 GSPerfMon: Use int rather than u64 for a frame count to match the rest of the core 2025-11-24 18:07:25 -05:00
Ty
8630893cb1 GIF: implicit cast to u32 warning fix 2025-11-24 18:07:25 -05:00
Ty
53598b970d IopBios: Use typedefs and explicit casting 2025-11-24 18:07:25 -05:00
Ty
89de00ac36 DEV9: Implicit size_t to int conversion warning fix 2025-11-24 18:07:25 -05:00
Ty
d5ddf07958 Counters: u32 implicit cast warning fix 2025-11-24 18:07:25 -05:00
PCSX2 Bot
30dcf4a14a [ci skip] PAD: Update to latest controller database. 2025-11-24 19:11:46 +01:00
JordanTheToaster
a87710e4bc GameDB: Simple 91/ All Star Fighters fixes 2025-11-24 19:11:33 +01:00
JordanTheToaster
a12f87fec2 Github: Use pip install yamllint 2025-11-24 16:57:40 +01:00
JordanTheToaster
8ba9bba094 ImGuiOverlays: Various text changes 2025-11-24 16:57:40 +01:00
JordanTheToaster
1363571c14 GameDB: Fixes of various kinds 2025-11-24 16:57:40 +01:00
chaoticgd
80de666fcc Debugger: Fix crash in getEEThreads 2025-11-24 16:52:15 +01:00
chaoticgd
ff0a2f84fa Config: Initialize AchievementsOptions bitset to zero 2025-11-24 16:12:27 +01:00
JordanTheToaster
0676f145bc Deps: Add additional fixes for Mac OS 11 compat
cheeseus
2025-11-24 16:11:40 +01:00
JordanTheToaster
e19ae2bf60 Deps: Update Qt to 6.10.1 2025-11-24 16:11:40 +01:00
lightningterror
7782d930d5 GS/HW: 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.
2025-11-24 16:09:46 +01:00
lightningterror
d1a53fe29b GS/DX11/GL: Move vertices for stencil date in SetupDATE.
The vertices are used only in SetupDATE so let's move them there.
VK/DX12 already do this.
2025-11-24 16:09:46 +01:00
lightningterror
c484cf286c GS/DX11: Don't unbind shader resource if depth stencil view is read only.
We don't need to unbind conflicting srv with dsv if the dsv itself is read only, it is used for depth testing and both can be bind at the same time.

Avoids re binding srv using a read only dsv.
2025-11-24 16:08:50 +01:00
PCSX2 Bot
f8882c4da6 [ci skip] Qt: Update Base Translation. 2025-11-22 06:31:36 +01:00
Ty
52c17e67a5 MipsStackWalk: Fix s64 -> u32 implicit casting warning 2025-11-21 18:20:58 -05:00
Ty
615cd00147 DEV9 / ATA: Fix u64 to u32 implicit casting warning 2025-11-21 18:20:58 -05:00
Ty
cb0bf953d3 Interpreter: Fix warning from reading 64 bit GPR into u32 variable.
The PS2 has 32 bit addresses!
2025-11-21 18:20:58 -05:00
Ty
26a68ef76a GSDumpReplayer: Fix possible memcpy out of bounds read and fix different size implicit casting warnings 2025-11-21 18:20:58 -05:00
Ty
e4c1dc2359 CDVD / Common: Use u32 for PSXCLK instead of u64 (yeah, it fixes warnings) 2025-11-21 18:20:58 -05:00
Ty
40425e3bee Translations: Warning fix, use qsizetype instead of int 2025-11-21 18:20:58 -05:00
Ty
e51e4a35fe MemoryCardSettings: Warning fix, use qsizetype instead of int 2025-11-21 18:20:58 -05:00
Ty
bd1b9ea718 Debugger: size_t -> int warning fix. Switched to u32. 2025-11-21 18:20:58 -05:00
Ty
faaa376232 Console: size_t -> u32 when constructing smallstring warning fix 2025-11-21 18:20:58 -05:00
Ty Lamontagne
e9ca1a6ead Image.cpp: Cast between types instead of using offsetof 2025-11-21 18:20:58 -05:00
Ty Lamontagne
4d3149eacb Host / Cubeb: Use size_t to iterate cubeb_backend_names 2025-11-21 18:20:58 -05:00
Ty Lamontagne
78822c96fb GSVK: Remove unused variable 2025-11-21 18:20:58 -05:00
Ty Lamontagne
a78617b987 Config: Fix GSOptions bitset comparison 2025-11-21 18:20:58 -05:00
Ty Lamontagne
3059ab2b12 QT GameList: Replace deprecated invalidateRowsFilter usage 2025-11-21 18:20:58 -05:00
Ty Lamontagne
1d0f6cc5b7 3rdparty/soundtouch: Fix -Wdeprecated-ofast warning 2025-11-21 18:20:58 -05:00
Ty Lamontagne
38a35043a8 GS/Patches: Explicitly cast non-trivially copyable types during memset / memcpy
Warning fixes
2025-11-21 18:20:58 -05:00
Ty Lamontagne
7385cbe40a Qt: Fix nodiscard warning when opening HTML files for the about dialog 2025-11-21 18:20:58 -05:00
oltolm
9955e07470 Misc: use std::lock_guard 2025-11-20 21:41:45 -05:00
Ty
74db386144 misc: big changes (jk the release machine just broke, need a new release me thinks) 2025-11-19 20:23:42 -05:00
PCSX2 Bot
3f72efeb7a [ci skip] Qt: Update Base Translation. 2025-11-20 01:07:57 +01:00
lightningterror
d0f8905439 Qt: Check if main window exists before updating performance metrics. 2025-11-20 00:06:59 +01:00
TheTechnician27
5a60259ef5 GameListWidget: Overhaul serialization of table header state 2025-11-19 15:06:21 +01:00
SternXD
c8dffccaa7 Image: Support loading bmp files
Signed-off-by: SternXD <stern@sidestore.io>

f
2025-11-19 15:03:44 +01:00
SternXD
87a82b16ff BPM: Add custom background support to Big Picture Mode
Signed-off-by: SternXD <stern@sidestore.io>
2025-11-19 15:03:44 +01:00
KamFretoZ
5666902638 Qt: Improve custom background scaling option 2025-11-19 15:01:29 +01:00
TJnotJT
f1dc232f91 GSRunner: Use correct config name to disable shader cache. 2025-11-19 02:50:01 +01:00
TheLastRar
5476c5a17f Qt: Don't detach surface from container when deleting the surface 2025-11-19 01:46:30 +01:00
lightningterror
9aabb197e6 GS/DX12: Misc Fixes.
Properly unbind slot 0 if previous draw was tex is fb or tex is ds.
Mirrors vk behavior.

Don't recycle draw_rt_clone in colclip, it's null at this point anyway.

Don't bind rt on slot 2 if we have multidraw fb copy enabled.
Mirrors barrier behavior on vk.
2025-11-19 01:45:42 +01:00
PCSX2 Bot
c2488c9269 [ci skip] Qt: Update Base Translation. 2025-11-19 01:03:29 +01:00
TheTechnician27
7e40ab8e7e OSD: More relevant save state timestamps 2025-11-18 18:11:46 +01:00
lightningterror
902b3c5033 GS/HW: Enable barrier date on alpha masked blend case.
If alpha write is masked and barrier/copy enabled then we can switch to DATE_BARRIER since it uses the destination alpha for testing anyway.

By default this is enabled on vk/gl but not on dx11/12 as copies are slow so we can enable it now since rt alpha is read anyway.
2025-11-18 15:19:43 +01:00
TheLastRar
4d1afb9fdd Qt: Handle display surface Drag & Drop events 2025-11-18 15:03:57 +01:00
chaoticgd
4209900351 Debugger: Fix some infinite loops 2025-11-17 07:17:57 +01:00
PCSX2 Bot
780c599b49 [ci skip] Qt: Update Base Translation. 2025-11-17 05:06:56 +01:00
TheLastRar
908d35bf77 Qt: Round window sizes after applying DPI scaling 2025-11-16 22:29:57 +01:00
TheLastRar
cfea84b934 Qt: Use QWindow as display surface 2025-11-16 22:29:57 +01:00
TheTechnician27
e5d94e255b OSD: Eliminate performance overlay flickering via caching 2025-11-16 22:16:38 +01:00
refractionpcsx2
080858b97c GS/HW: Apply native scaling to exact GPU CLUTs 2025-11-16 22:10:22 +01:00
TheLastRar
d883076573 GSRunner: Move more of VM setup to CPU thread 2025-11-16 22:08:13 +01:00
chaoticgd
b80101fbd6 Debugger: Rewrite menu bar layout logic 2025-11-16 22:02:59 +01:00
refractionpcsx2
aca775f8b8 GS/HW: Reintroduce slightly more aggressive region offset for Align to Native 2025-11-16 21:36:14 +01:00
PCSX2 Bot
4f4a26769c [ci skip] Qt: Update Base Translation. 2025-11-16 21:29:11 +01:00
Wes Copeland
d19eaa1b8e Achievements: Support a custom host 2025-11-16 15:27:22 -05:00
TJnotJT
be1af0cd0f GS: Allow dumping draw/frames stats. 2025-11-16 21:26:10 +01:00
TheTechnician27
6ab02e76f1 Docs: Remove Debugger.md 2025-11-15 14:55:28 -05:00
TheTechnician27
f87bc7d72b UI: Make 'Start fullscreen' option work for BPM and improve variable name
Co-authored-by: Geraldi Kusuma Arnanto <981538+aldee@users.noreply.github.com>
2025-11-15 13:37:21 -05:00
chaoticgd
086f4f11e1 Qt: Prevent entering/exiting fullscreen while the VM is locked 2025-11-15 13:31:23 -05:00
TJnotJT
6f54da6234 GS/HW/TC: Do not force temporary source creation for PSMT8 sources that request outside a target valid area. 2025-11-15 16:56:57 +01:00
TJnotJT
44f47f11b8 GS/HW/TC: Force a temporary source creation in edges cases.
Case: When looking up a source, we find a perfect BP hit for a target.
However, the requested area is outside the target's valid area.
Don't use the target direct and instead load from memory in a temporary source.

Co-authored-by: refraction
2025-11-15 16:56:57 +01:00
TJnotJT
b5a2d04b2e GS/HW/TC: Remove outside target PSM check in LookupSource.
Co-autored-by: refraction
2025-11-15 16:56:57 +01:00
TJnotJT
8508ebb7d3 GS/HW: Remove legacy code for changing RT from depth to color in TC invalidation.
Co-authored-by: refraction
2025-11-15 16:56:57 +01:00
lightningterror
3234e45f33 GS/HW: Also check if blend will update the rt.
If it doesn't and there is no depth buffer we can abort the draw(s) safely.
2025-11-15 16:54:12 +01:00
lightningterror
53d1320d83 GS/HW: Re check if RT is written after we know the source alpha. 2025-11-15 16:54:12 +01:00
TheTechnician27
9b545809be Docs: Remove debugger.txt and Debugger.pdf 2025-11-14 20:46:06 -05:00
chaoticgd
79400acf2a Build: Add VectorIntrin.h to precompiled header 2025-11-14 08:03:27 -05:00
PCSX2 Bot
3107c4103a [ci skip] Qt: Update Base Translation. 2025-11-12 19:06:36 -05:00
chaoticgd
68c88f692e Qt: Fix use-after-free in GameSummaryWidget 2025-11-12 18:51:57 -05:00
chaoticgd
df19b37d6d Qt: Fix game list deadlock when changing discs 2025-11-12 18:51:57 -05:00
chaoticgd
1b5c352566 Debugger: Improve symbol tree enum editing 2025-11-12 18:46:10 -05:00
PCSX2 Bot
bed6a9e4d4 [ci skip] Qt: Update Base Translation. 2025-11-12 01:03:38 +01:00
lightningterror
d602ad1d3e GS/HW: Purge no longer needed/replaced crc hacks GSC_ZettaiZetsumeiToshi2, GSC_SteambotChronicles. 2025-11-11 22:36:46 +01:00
refractionpcsx2
51c31347df GS/HW: Remove clamp hack for edge garbage.
Not seemingly required anymore, might be because NS is a thing.
2025-11-11 22:36:46 +01:00
refractionpcsx2
00876e7076 GameDB: Fixes for later IRem games 2025-11-11 22:36:46 +01:00
refractionpcsx2
47eb499893 GS/HW: Add CRC hack to cover IRem games to deal with depth deswizzle shuffle 2025-11-11 22:36:46 +01:00
refractionpcsx2
b8680c3139 GS/HW: Mask channel offsets in channel shuffle heuristic 2025-11-11 22:36:46 +01:00
refractionpcsx2
9b4e3b8f74 GS/HW: Adjust target lookup to better check on source match 2025-11-11 22:36:46 +01:00
refractionpcsx2
3e858167bc Build: Update gamedb documentation and linter 2025-11-11 21:19:02 +01:00
refractionpcsx2
44d66555cc GS/HW: Add options to maintain upscale in Native Scaling
This will adjust how the box filter samples to simulate bilinear when upscaling
2025-11-11 21:19:02 +01:00
refractionpcsx2
c5438ceca3 GS/HW: Fix up invalidly selected Tex in RT targets 2025-11-11 20:58:35 +01:00
JordanTheToaster
3e1927ae44 GameDB: Ore no Shita de Agake fixes 2025-11-11 14:59:04 +01:00
TheTechnician27
b688117002 RTC: Automatically detect timezone 2025-11-10 20:14:36 -06:00
dependabot[bot]
e62e6fb6c3 Bump softprops/action-gh-release in the ci-deps group
Bumps the ci-deps group with 1 update: [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `softprops/action-gh-release` from 2.4.1 to 2.4.2
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](6da8fa9354...5be0e66d93)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ci-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 19:28:32 -05:00
PCSX2 Bot
262e94e5d7 [ci skip] PAD: Update to latest controller database. 2025-11-10 20:01:35 +01:00
PCSX2 Bot
11a4b4e7ff [ci skip] Qt: Update Base Translation. 2025-11-10 01:07:34 +01:00
chaoticgd
a98cfcf28c CDVD: Synchronise access from different threads 2025-11-09 09:27:27 -05:00
PCSX2 Bot
d02f30ee62 [ci skip] Qt: Update Base Translation. 2025-11-08 18:23:15 -06:00
JordanTheToaster
c0bf01a646 MacOS: Patch Tahoe unclickable buttons 2025-11-08 08:31:46 -05:00
TheTechnician27
babb985e9e GS Hotkeys: Improve upscale and downscale hotkeys 2025-11-07 20:28:27 -05:00
PCSX2 Bot
e379c8317d [ci skip] Qt: Update Base Translation. 2025-11-07 03:49:56 +01:00
chaoticgd
5098277474 Debugger: Fix 1 pixel gap under layout tabs on non-Windows platforms 2025-11-06 09:32:29 -05:00
TheLastRar
4a94cb6cbd DEV9: Remove using namespace in TAPAdapter header 2025-11-05 16:44:10 -05:00
TheTechnician27
e245454b91 GameListWidget: Fix icon cell highlights and column widths 2025-11-05 16:43:28 -05:00
TheTechnician27
b003eadd2d VMManager: More helpful error message on no BIOS present 2025-11-03 12:52:28 -05:00
JordanTheToaster
a5984d8213 Flatpak: Update KDE runtime to 6.10 2025-11-03 12:49:35 -05:00
Stern
4dbd95b0bb labeler: Add OSD / ImGui labeler 2025-11-03 10:38:26 -05:00
JordanTheToaster
68803229da Core: Bump savestate version.
[SAVEVERSION+]
2025-11-02 20:19:38 -05:00
chaoticgd
b661a2a149 Deps: Update KDDockWidgets to 2.4.0 2025-11-02 15:02:25 -05:00
PCSX2 Bot
63cd355d7a [ci skip] Qt: Update Base Translation. 2025-11-02 14:43:04 -05:00
Ziemas
0f5ff68679 SPU: Remove KON delay/check key once per T 2025-11-02 14:36:54 -05:00
JordanTheToaster
bd74921926 Deps: Update SDL3 to v3.2.26 2025-11-01 11:32:32 -04:00
TheLastRar
78f83514f4 Deps: Force lib directory for libjpeg-turbo build 2025-11-01 11:31:20 -04:00
TheTechnician27
2c36259b88 DB: Remove some unsupported or nonexistent serials 2025-11-01 11:29:27 -04:00
TheTechnician27
32a0bed6af GameListModel: Touch up a couple functions 2025-11-01 11:28:59 -04:00
TheTechnician27
d415f8364c Snapshots: Clean up per-game snapshots folder code 2025-11-01 11:14:55 -04:00
144 changed files with 7196 additions and 5293 deletions

7
.github/labeler.yml vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -20,12 +20,12 @@ LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.2
LIBPNG=1.6.50
LIBWEBP=1.6.0
SDL=SDL3-3.2.24
QT=6.10.0
SDL=SDL3-3.2.26
QT=6.10.1
QTAPNG=1.3.0
LZ4=1.10.0
ZSTD=1.5.7
KDDOCKWIDGETS=2.3.0
KDDOCKWIDGETS=2.4.0
PLUTOVG=1.3.1
PLUTOSVG=0.0.7
@@ -44,22 +44,22 @@ fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
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
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
EOF
@@ -116,7 +116,9 @@ echo "Building libjpegturbo..."
rm -fr "libjpeg-turbo-$LIBJPEGTURBO"
tar xf "libjpeg-turbo-$LIBJPEGTURBO.tar.gz"
cd "libjpeg-turbo-$LIBJPEGTURBO"
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -B build -G Ninja
# On non debian or debian based Linux systems, libjpeg-turbo will set CMAKE_INSTALL_DEFAULT_LIBDIR "lib64" (or libx32)
# That will prevent CMake from finding the deps libjpeg later on. if we set CMAKE_INSTALL_DEFAULT_LIBDIR, libjpeg-turbo will leave it as is, so set it to "lib"
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DENABLE_STATIC=OFF -DENABLE_SHARED=ON -DCMAKE_INSTALL_DEFAULT_LIBDIR="lib" -B build -G Ninja
cmake --build build --parallel
ninja -C build install
cd ..

View File

@@ -14,8 +14,8 @@
"sources": [
{
"type": "archive",
"url": "https://libsdl.org/release/SDL3-3.2.24.tar.gz",
"sha256": "81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76"
"url": "https://libsdl.org/release/SDL3-3.2.26.tar.gz",
"sha256": "dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2"
}
],
"cleanup": [

View File

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

View File

@@ -40,7 +40,7 @@ fi
FREETYPE=2.14.1
HARFBUZZ=12.0.0
SDL=SDL3-3.2.24
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.50
@@ -48,9 +48,9 @@ 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.3.0
KDDOCKWIDGETS=2.4.0
PLUTOVG=1.3.1
PLUTOSVG=0.0.7
@@ -80,7 +80,7 @@ CMAKE_ARCH_UNIVERSAL=-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
cat > SHASUMS <<EOF
32427e8c471ac095853212a37aef816c60b42052d4d9e48230bab3bdf2936ccc freetype-$FREETYPE.tar.xz
c4a398539c3e0fdc9a82dfe7824d0438cae78c1e2124e7c6ada3dfa600cdb6c8 harfbuzz-$HARFBUZZ.tar.gz
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
@@ -89,17 +89,17 @@ 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
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
EOF

View File

@@ -22,7 +22,7 @@ fi
FREETYPE=2.14.1
HARFBUZZ=12.0.0
SDL=SDL3-3.2.24
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.50
@@ -30,9 +30,9 @@ 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.3.0
KDDOCKWIDGETS=2.4.0
PLUTOVG=1.3.1
PLUTOSVG=0.0.7
@@ -61,7 +61,7 @@ CMAKE_COMMON=(
cat > SHASUMS <<EOF
32427e8c471ac095853212a37aef816c60b42052d4d9e48230bab3bdf2936ccc freetype-$FREETYPE.tar.xz
c4a398539c3e0fdc9a82dfe7824d0438cae78c1e2124e7c6ada3dfa600cdb6c8 harfbuzz-$HARFBUZZ.tar.gz
81cc0fc17e5bf2c1754eeca9af9c47a76789ac5efdd165b3b91cbbe4b90bfb76 $SDL.tar.gz
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
4df396518620a7aa3651443e87d1b2862e4e88cad135a8b93423e01706232307 libpng-$LIBPNG.tar.xz
@@ -70,17 +70,17 @@ 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
c2225a49c3d7efa5c4f4ce4a6b42081e6ea3daca376f3353d9d7c2722d77a28a shaderc-spirv-headers-$SHADERC_SPIRVHEADERS.tar.gz
44d1005880c583fc00a0fb41c839214c68214b000ea8dcb54d352732fee600ff shaderc-spirv-tools-$SHADERC_SPIRVTOOLS.tar.gz
843baf9e1812c1ab82fd81d85b57cbc0d29bb43245efeb2539039780004b1056 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
bea672eb96ee36c2cbeb911b9bac66dfe989b3ad9a9943101e00aeb2df2aefdb plutovg-$PLUTOVG.tar.gz
78561b571ac224030cdc450ca2986b4de915c2ba7616004a6d71a379bffd15f3 plutosvg-$PLUTOSVG.tar.gz
EOF

View File

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

View File

@@ -47,8 +47,8 @@ set HARFBUZZ=12.0.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1650
set LIBPNGLONG=1.6.50
set SDL=SDL3-3.2.24
set QT=6.10.0
set SDL=SDL3-3.2.26
set QT=6.10.1
set QTMINOR=6.10
set QTAPNG=1.3.0
set LZ4=1.10.0
@@ -56,7 +56,7 @@ set WEBP=1.6.0
set ZLIB=1.3.1
set ZLIBSHORT=131
set ZSTD=1.5.7
set KDDOCKWIDGETS=2.3.0
set KDDOCKWIDGETS=2.4.0
set PLUTOVG=1.3.1
set PLUTOSVG=0.0.7
@@ -71,18 +71,17 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" ca7fe2ca54a97e047f5eff236e62ae87546e862f509f0a62fc6e564ded3c6a95 || goto error
call :downloadfile "SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" https://github.com/libsdl-org/SDL/commit/7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch 5c09a29a9cac87a85ec3b8f5a68ff0e5c6c47229f3e2282241b9c32166f26977 || 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 "%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" 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
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" d2b9592ebe5d053ac97a0213ea35139866d8d5e0a1d84b7d3fb581db7f0b01c6 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 615184f756d91ce416f2cf883bb67fd4262651417c2e40c4d681c8641a48263e || goto error
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
@@ -189,7 +188,6 @@ echo Building SDL...
rmdir /S /Q "%SDL%"
%SEVENZIP% x "%SDL%.zip" || goto error
cd "%SDL%" || goto error
%PATCH% -p1 < "..\SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" || goto error
cmake -B build %ARM64TOOLCHAIN% -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error

View File

@@ -44,9 +44,9 @@ set FREETYPE=2.14.1
set HARFBUZZ=12.0.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1650
set SDL=SDL3-3.2.24
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
@@ -54,7 +54,7 @@ set WEBP=1.6.0
set ZLIB=1.3.1
set ZLIBSHORT=131
set ZSTD=1.5.7
set KDDOCKWIDGETS=2.3.0
set KDDOCKWIDGETS=2.4.0
set PLUTOVG=1.3.1
set PLUTOSVG=0.0.7
@@ -69,18 +69,17 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" ca7fe2ca54a97e047f5eff236e62ae87546e862f509f0a62fc6e564ded3c6a95 || goto error
call :downloadfile "SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" https://github.com/libsdl-org/SDL/commit/7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch 5c09a29a9cac87a85ec3b8f5a68ff0e5c6c47229f3e2282241b9c32166f26977 || 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 "%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" 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
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" d2b9592ebe5d053ac97a0213ea35139866d8d5e0a1d84b7d3fb581db7f0b01c6 || goto error
call :downloadfile "KDDockWidgets-%KDDOCKWIDGETS%.zip" "https://github.com/KDAB/KDDockWidgets/archive/v%KDDOCKWIDGETS%.zip" 47ddb48197872055f0adf8e90a7235f8a3b795ca1ee3a28ac2c504c673ae3806 || goto error
call :downloadfile "plutovg-%PLUTOVG%.zip" "https://github.com/sammycage/plutovg/archive/v%PLUTOVG%.zip" 615184f756d91ce416f2cf883bb67fd4262651417c2e40c4d681c8641a48263e || goto error
call :downloadfile "plutosvg-%PLUTOSVG%.zip" "https://github.com/sammycage/plutosvg/archive/v%PLUTOSVG%.zip" 82dee2c57ad712bdd6d6d81d3e76249d89caa4b5a4214353660fd5adff12201a || goto error
@@ -186,7 +185,6 @@ echo Building SDL...
rmdir /S /Q "%SDL%"
%SEVENZIP% x "%SDL%.zip" || goto error
cd "%SDL%" || goto error
%PATCH% -p1 < "..\SDL-7914bdb7ea14ee5109d50df857c8dfc69a28a62d.patch" || goto error
cmake -B build -DCMAKE_BUILD_TYPE=Release %FORCEPDB% -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DSDL_SHARED=ON -DSDL_STATIC=OFF -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -106,6 +106,11 @@ namespace Common
Reset();
}
Timer::Timer(Value start_value)
{
m_tvStartValue = start_value;
}
void Timer::Reset()
{
m_tvStartValue = GetCurrentValue();

View File

@@ -12,6 +12,7 @@ namespace Common
using Value = std::uint64_t;
Timer();
Timer (Value start_value);
static Value GetCurrentValue();
static double ConvertValueToSeconds(Value value);

View File

@@ -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, &params);
std::atomic<int> thread_ret;
std::thread cputhread(CPUThreadMain, &params, &thread_ret);
GSRunner::PumpPlatformMessages(/*forever=*/true);
cputhread.join();
VMManager::Internal::CPUThreadShutdown();
GSRunner::DestroyPlatformWindow();
return EXIT_SUCCESS;
return thread_ret.load();
}
void Host::PumpMessagesOnCPUThread()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -145,15 +145,8 @@ DockTabBar::DockTabBar(KDDockWidgets::Core::TabBar* controller, QWidget* parent)
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &DockTabBar::customContextMenuRequested, this, &DockTabBar::openContextMenu);
// The constructor of KDDockWidgets::QtWidgets::TabBar makes a QProxyStyle
// that ends up taking ownerhsip of the style for the entire application!
if (QProxyStyle* proxy_style = qobject_cast<QProxyStyle*>(style()))
{
if (proxy_style->baseStyle() == qApp->style())
proxy_style->baseStyle()->setParent(qApp);
proxy_style->setBaseStyle(QStyleFactory::create(qApp->style()->name()));
}
}
void DockTabBar::openContextMenu(QPoint pos)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,51 +31,6 @@ static constexpr int SIZE_HINT_HEIGHT_TITLES = SIZE_HINT_HEIGHT + COVER_ART_SPAC
static constexpr int MIN_COVER_CACHE_SIZE = 256;
static int DPRScale(int size, qreal dpr)
{
return static_cast<int>(static_cast<qreal>(size) * dpr);
}
static int DPRUnscale(int size, qreal dpr)
{
return static_cast<int>(static_cast<qreal>(size) / dpr);
}
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, 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, int width, int height, float scale,
qreal dpr, const std::string& title)
{
@@ -84,7 +39,7 @@ static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int wid
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))
{
@@ -102,9 +57,9 @@ static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int wid
return pm;
}
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::string_view name)
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(const std::string_view name)
{
for (int column = 0; column < Column_Count; column++)
for (u32 column = 0; column < Column_Count; column++)
{
if (name == s_column_names[column])
return static_cast<Column>(column);
@@ -113,12 +68,12 @@ std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::stri
return std::nullopt;
}
const char* GameListModel::getColumnName(Column col)
const char* GameListModel::getColumnName(const Column col)
{
return s_column_names[static_cast<int>(col)];
}
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, qreal dpr, QObject* parent /* = nullptr */)
GameListModel::GameListModel(const float cover_scale, const bool show_cover_titles, const qreal dpr, QObject* parent /* = nullptr */)
: QAbstractTableModel(parent)
, m_show_titles_for_covers(show_cover_titles)
, m_dpr{dpr}
@@ -181,29 +136,31 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
QFuture<QPixmap> future = QtConcurrent::run([this, entry = *ge, counter]() -> QPixmap {
QPixmap image;
// Initial check that the scale is unchanged before we run costly image generation.
if (m_cover_scale_counter.load(std::memory_order_acquire) == counter)
{
const std::string cover_path(GameList::GetCoverImagePathForEntry(&entry));
if (!cover_path.empty())
{
image = QPixmap(QString::fromStdString(cover_path));
if (!image.isNull())
{
image.setDevicePixelRatio(m_dpr);
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
}
// Create placeholder image if no user-provided cover exists.
if (image.isNull())
{
const std::string& title = entry.GetTitle(m_prefer_english_titles);
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, m_dpr, title);
}
// Create resized image from user-provided cover.
else
{
image.setDevicePixelRatio(m_dpr);
QtUtils::resizeAndScalePixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr, QtUtils::ScalingMode::Fit, 100);
}
}
const std::string& title = entry.GetTitle(m_prefer_english_titles);
// Final check that scale is unchanged before we send out the produced image.
return m_cover_scale_counter.load(std::memory_order_acquire) == counter ? image : QPixmap();
if (image.isNull())
image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, m_dpr, title);
if (m_cover_scale_counter.load(std::memory_order_acquire) != counter)
image = {};
return image;
});
// Context must be 'this' so we run on the UI thread.
@@ -219,25 +176,18 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
void GameListModel::invalidateCoverForPath(const std::string& path)
{
// This isn't ideal, but not sure how else we can get the row, when it might change while scanning...
auto lock = GameList::GetLock();
const auto lock = GameList::GetLock();
const u32 count = GameList::GetEntryCount();
std::optional<u32> row;
for (u32 i = 0; i < count; i++)
for (u32 row = 0; row < count; row++)
{
if (GameList::GetEntryByIndex(i)->path == path)
if (GameList::GetEntryByIndex(row)->path == path)
{
row = i;
break;
const QModelIndex mi(index(row, Column_Cover));
emit dataChanged(mi, mi, {Qt::DecorationRole});
return;
}
}
if (!row.has_value())
{
// Game removed?
return;
}
const QModelIndex mi(index(static_cast<int>(row.value()), Column_Cover));
emit dataChanged(mi, mi, {Qt::DecorationRole});
}
int GameListModel::getCoverArtWidth() const
@@ -257,18 +207,12 @@ int GameListModel::getCoverArtSpacing() const
int GameListModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return static_cast<int>(GameList::GetEntryCount());
return parent.isValid() ? 0 : static_cast<int>(GameList::GetEntryCount());
}
int GameListModel::columnCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return Column_Count;
return parent.isValid() ? 0 : Column_Count;
}
QString GameListModel::formatTimespan(const time_t timespan)
@@ -285,7 +229,7 @@ QString GameListModel::formatTimespan(const time_t timespan)
return qApp->translate("GameList", "%n seconds", "", static_cast<u32>((timespan % 3600) % 60));
}
QVariant GameListModel::data(const QModelIndex& index, int role) const
QVariant GameListModel::data(const QModelIndex& index, const int role) const
{
if (!index.isValid())
return QVariant();
@@ -357,8 +301,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
if (pm)
return *pm;
// We insert the placeholder into the cache, so that we don't repeatedly
// queue loading jobs for this game.
// Insert the placeholder into the cache so we don't repeatedly queue loading jobs for this game.
const_cast<GameListModel*>(this)->loadOrGenerateCover(ge);
return *m_cover_pixmap_cache.Insert(ge->path, m_loading_pixmap);
}
@@ -386,7 +329,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
}
}
QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
QVariant GameListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
{
if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= Column_Count)
return QVariant();
@@ -401,7 +344,7 @@ void GameListModel::refresh()
endResetModel();
}
bool GameListModel::titlesLessThan(int left_row, int right_row) const
bool GameListModel::titlesLessThan(const int left_row, const int right_row) const
{
if (left_row < 0 || left_row >= static_cast<int>(GameList::GetEntryCount()) || right_row < 0 ||
right_row >= static_cast<int>(GameList::GetEntryCount()))
@@ -415,7 +358,7 @@ bool GameListModel::titlesLessThan(int left_row, int right_row) const
QString::fromStdString(right->GetTitleSort(m_prefer_english_titles))) < 0;
}
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const
bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, const int column) const
{
if (!left_index.isValid() || !right_index.isValid())
return false;
@@ -448,13 +391,12 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
{
if (left->serial == right->serial)
return titlesLessThan(left_row, right_row);
return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0);
}
case Column_Title:
{
return titlesLessThan(left_row, right_row);
}
case Column_FileTitle:
{
@@ -471,6 +413,7 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r
{
if (left->region == right->region)
return titlesLessThan(left_row, right_row);
return (static_cast<int>(left->region) < static_cast<int>(right->region));
}
@@ -524,7 +467,7 @@ void GameListModel::loadSettings()
m_prefer_english_titles = Host::GetBaseBoolSettingValue("UI", "PreferEnglishGameList", false);
}
QIcon GameListModel::getIconForType(GameList::EntryType type)
QIcon GameListModel::getIconForType(const GameList::EntryType type)
{
switch (type)
{
@@ -538,7 +481,7 @@ QIcon GameListModel::getIconForType(GameList::EntryType type)
}
}
QIcon GameListModel::getIconForRegion(GameList::Region region)
QIcon GameListModel::getIconForRegion(const GameList::Region region)
{
return QIcon(
QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(GameList::RegionToString(region, false)));
@@ -549,8 +492,8 @@ void GameListModel::loadThemeSpecificImages()
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
m_type_pixmaps[type] = getIconForType(static_cast<GameList::EntryType>(type)).pixmap(QSize(24, 24), m_dpr);
for (u32 i = 0; i < static_cast<u32>(GameList::Region::Count); i++)
m_region_pixmaps[i] = getIconForRegion(static_cast<GameList::Region>(i)).pixmap(QSize(36, 26), m_dpr);
for (u32 region = 0; region < static_cast<u32>(GameList::Region::Count); region++)
m_region_pixmaps[region] = getIconForRegion(static_cast<GameList::Region>(region)).pixmap(QSize(36, 26), m_dpr);
}
void GameListModel::loadCommonImages()
@@ -558,8 +501,8 @@ void GameListModel::loadCommonImages()
loadThemeSpecificImages();
const QString base_path(QtHost::GetResourcesBasePath());
for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++)
m_compatibility_pixmaps[i] = QIcon((QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(i - 1))).pixmap(QSize(88, 16), m_dpr);
for (u32 rating = 1; rating < GameList::CompatibilityRatingCount; rating++)
m_compatibility_pixmaps[rating] = QIcon((QStringLiteral("%1/icons/star-%2.svg").arg(base_path).arg(rating - 1))).pixmap(QSize(88, 16), m_dpr);
m_placeholder_pixmap.load(QStringLiteral("%1/cover-placeholder.png").arg(base_path));
}

View File

@@ -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)
{
}

View File

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

View File

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

View File

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

View File

@@ -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 Screenshots Folder"));
connect(action, &QAction::triggered, [this, entry]() { openScreenshotsFolderForGame(entry); });
action = menu.addAction(tr("Open Snapshots Folder"));
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,56 +2850,47 @@ 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::openScreenshotsFolderForGame(const GameList::Entry* entry)
void MainWindow::openSnapshotsFolderForGame(const GameList::Entry& entry)
{
if (!entry || entry->title.empty())
return;
// if disabled open the snapshots folder
if (!EmuConfig.GS.OrganizeScreenshotsByGame)
// Go to top-level snapshots directory if not organizing by game.
if (EmuConfig.GS.OrganizeSnapshotsByGame && !entry.title.empty())
{
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::Snapshots)));
return;
}
std::string game_name = entry.title;
Path::SanitizeFileName(&game_name);
std::string game_name = entry->title;
Path::SanitizeFileName(&game_name);
if (game_name.length() > 219)
{
game_name.resize(219);
}
const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name);
const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name);
if (!FileSystem::DirectoryExists(game_dir.c_str()))
{
if (!FileSystem::CreateDirectoryPath(game_dir.c_str(), false))
// Make sure the per-game directory exists or that we can successfully create it.
if (FileSystem::DirectoryExists(game_dir.c_str()) || FileSystem::CreateDirectoryPath(game_dir.c_str(), false))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to create screenshots directory '%1'.").arg(QString::fromStdString(game_dir)));
const QFileInfo fi(QString::fromStdString(game_dir));
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absoluteFilePath()));
return;
}
QMessageBox::critical(this, tr("Error"), tr("Failed to create snapshots directory '%1'\n\nOpening default directory.").arg(QString::fromStdString(game_dir)));
}
const QFileInfo fi(QString::fromStdString(game_dir));
QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absoluteFilePath()));
QtUtils::OpenURL(this, QUrl::fromLocalFile(QString::fromStdString(EmuFolders::Snapshots)));
}
std::optional<bool> MainWindow::promptForResumeState(const QString& save_state_path)
@@ -3035,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);
}
}
@@ -3265,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
@@ -3284,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)
@@ -3312,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;
@@ -3341,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;

View File

@@ -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 openScreenshotsFolderForGame(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;

View File

@@ -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())
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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."));

View File

@@ -19,14 +19,13 @@ FolderSettingsWidget::FolderSettingsWidget(SettingsWindow* settings_dialog, QWid
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.cheats, m_ui.cheatsBrowse, m_ui.cheatsOpen, m_ui.cheatsReset, "Folders", "Cheats", Path::Combine(EmuFolders::DataRoot, "cheats"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.covers, m_ui.coversBrowse, m_ui.coversOpen, m_ui.coversReset, "Folders", "Covers", Path::Combine(EmuFolders::DataRoot, "covers"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.snapshots, m_ui.snapshotsBrowse, m_ui.snapshotsOpen, m_ui.snapshotsReset, "Folders", "Snapshots", Path::Combine(EmuFolders::DataRoot, "snaps"));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeScreenshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false);
connect(m_ui.organizeScreenshotsByGame, &QCheckBox::checkStateChanged, this, [](int state) {
GSConfig.OrganizeScreenshotsByGame = (state == Qt::Checked);
});
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset, "Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset, "Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos"));
dialog()->registerWidgetHelp(m_ui.organizeScreenshotsByGame, tr("Organize Screenshots by Game"), tr("Unchecked"),
tr("When enabled, screenshots will be saved in a folder with the game's name, instead of all being saved in the Snapshots folder"));
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.organizeSnapshotsByGame, "EmuCore/GS", "OrganizeScreenshotsByGame", false);
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.saveStates, m_ui.saveStatesBrowse, m_ui.saveStatesOpen, m_ui.saveStatesReset,
"Folders", "SaveStates", Path::Combine(EmuFolders::DataRoot, "sstates"));
SettingWidgetBinder::BindWidgetToFolderSetting(sif, m_ui.videoDumpingDirectory, m_ui.videoDumpingDirectoryBrowse, m_ui.videoDumpingDirectoryOpen, m_ui.videoDumpingDirectoryReset,
"Folders", "Videos", Path::Combine(EmuFolders::DataRoot, "videos"));
dialog()->registerWidgetHelp(m_ui.organizeSnapshotsByGame, tr("Organize Snapshots by Game"), tr("Unchecked"),
tr("Saves snapshots to per-game subfolders instead of a shared folder."));
}
FolderSettingsWidget::~FolderSettingsWidget() = default;

View File

@@ -147,7 +147,7 @@
</widget>
</item>
<item row="2" column="0" colspan="4">
<widget class="QCheckBox" name="organizeScreenshotsByGame">
<widget class="QCheckBox" name="organizeSnapshotsByGame">
<property name="text">
<string>Save Snapshots in Game-Specific Folders</string>
</property>
@@ -322,7 +322,7 @@
<tabstop>snapshotsBrowse</tabstop>
<tabstop>snapshotsOpen</tabstop>
<tabstop>snapshotsReset</tabstop>
<tabstop>organizeScreenshotsByGame</tabstop>
<tabstop>organizeSnapshotsByGame</tabstop>
<tabstop>saveStates</tabstop>
<tabstop>saveStatesBrowse</tabstop>
<tabstop>saveStatesOpen</tabstop>

View File

@@ -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);
}

View File

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

View File

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

View File

@@ -970,7 +970,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
m_texture.precacheTextureReplacements->setEnabled(enabled);
}
void GraphicsSettingsWidget::onCaptureContainerChanged()
{
const std::string container(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
}

View File

@@ -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 */)())
{
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
@@ -789,7 +793,7 @@ struct Pcsx2Config
VideoCaptureAutoResolution : 1,
EnableAudioCapture : 1,
EnableAudioCaptureParameters : 1,
OrganizeScreenshotsByGame : 1;
OrganizeSnapshotsByGame : 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,

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@
#include <vector>
#include <string>
#include "..\net.h"
using namespace std;
class TAPAdapter : public NetAdapter
{

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -242,7 +242,7 @@
"nativeScaling": {
"type": "integer",
"minimum": 0,
"maximum": 2
"maximum": 4
},
"roundSprite": {
"type": "integer",

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
@@ -827,20 +834,16 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
}
}
void GSRenderer::QueueSnapshot(const std::string& path, u32 gsdump_frames)
void GSRenderer::QueueSnapshot(const std::string& path, const u32 gsdump_frames)
{
if (!m_snapshot.empty())
return;
// Allows for providing a complete path
if (path.size() > 4 && StringUtil::EndsWithNoCase(path, ".png"))
{
m_snapshot = path.substr(0, path.size() - 4);
}
else
{
m_snapshot = GSGetBaseSnapshotFilename();
}
// this is really gross, but wx we get the snapshot request after shift...
m_dump_frames = gsdump_frames;
@@ -894,31 +897,22 @@ static std::string GSGetBaseFilename()
std::string GSGetBaseSnapshotFilename()
{
// prepend snapshots directory
std::string base_path = EmuFolders::Snapshots;
// If organize by game is enabled, create a game-specific folder
if (GSConfig.OrganizeScreenshotsByGame)
// If organize by game is enabled, use or create a game-specific folder.
if (GSConfig.OrganizeSnapshotsByGame)
{
std::string game_name = VMManager::GetTitle(true);
if (!game_name.empty())
{
Path::SanitizeFileName(&game_name);
if (game_name.length() > 219)
{
game_name.resize(219);
}
const std::string game_dir = Path::Combine(base_path, game_name);
if (!FileSystem::DirectoryExists(game_dir.c_str()))
{
FileSystem::CreateDirectoryPath(game_dir.c_str(), false);
}
const std::string game_dir = Path::Combine(EmuFolders::Snapshots, game_name);
base_path = game_dir;
// Make sure the per-game directory exists or that we can successfully create it.
if (FileSystem::DirectoryExists(game_dir.c_str()) || FileSystem::CreateDirectoryPath(game_dir.c_str(), false))
return Path::Combine(game_dir, GSGetBaseFilename());
}
}
return Path::Combine(base_path, GSGetBaseFilename());
return Path::Combine(EmuFolders::Snapshots, GSGetBaseFilename());
}
std::string GSGetBaseVideoFilename()

View File

@@ -59,7 +59,7 @@ public:
bool SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders,
u32* width, u32* height, std::vector<u32>* pixels);
void QueueSnapshot(const std::string& path, u32 gsdump_frames);
void QueueSnapshot(const std::string& path, const u32 gsdump_frames);
void StopGSDump();
void PresentCurrentFrame();
bool BeginCapture(std::string filename, const GSVector2i& size = GSVector2i(0, 0));

View File

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

View File

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

View File

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

View File

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

View File

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

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