Compare commits

...

62 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
75 changed files with 4720 additions and 3105 deletions

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

@@ -21,7 +21,7 @@ LIBJPEGTURBO=3.1.2
LIBPNG=1.6.50
LIBWEBP=1.6.0
SDL=SDL3-3.2.26
QT=6.10.0
QT=6.10.1
QTAPNG=1.3.0
LZ4=1.10.0
ZSTD=1.5.7
@@ -48,12 +48,12 @@ dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
687ddc0c7cb128a3ea58e159b5129252537c27ede0c32a93f11f03127f0c0165 libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
603f2b0a259b24bd0fb14f880d7761b1d248118a42a6870cdbe8fdda4173761f qtwayland-everywhere-src-$QT.tar.xz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
49bf6db800227a6b2c971f4c5d03dd1e81297e7ffb296ce4a96437304f27cb13 qtwayland-everywhere-src-$QT.tar.xz
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz

View File

@@ -48,7 +48,7 @@ LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
QT=6.10.0
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
PLUTOVG=1.3.1
@@ -89,11 +89,11 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz

View File

@@ -30,7 +30,7 @@ LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
MOLTENVK=1.2.9
QT=6.10.0
QT=6.10.1
QTAPNG=1.3.0
KDDOCKWIDGETS=2.4.0
PLUTOVG=1.3.1
@@ -70,11 +70,11 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz
ead4623bcb54a32257c5b3e3a5aec6d16ec96f4cda58d2e003f5a0c16f72046d qtbase-everywhere-src-$QT.tar.xz
64450a52507c540de53616ed5e516df0e0905a99d3035ddfaa690f2b3f7c0cea qtimageformats-everywhere-src-$QT.tar.xz
5ed2c0e04d5e73ff75c2a2ed92db5dc1788ba70f704fc2b71bc21644beda2533 qtsvg-everywhere-src-$QT.tar.xz
d86d5098cf3e3e599f37e18df477e65908fc8f036e10ea731b3469ec4fdbd02a qttools-everywhere-src-$QT.tar.xz
326e8253cfd0cb5745238117f297da80e30ce8f4c1db81990497bd388b026cde qttranslations-everywhere-src-$QT.tar.xz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz
498eabdf2381db96f808942b3e3c765f6360fe6c0e9961f0a45ff7a4c68d7a72 qtimageformats-everywhere-src-$QT.tar.xz
c02f355a58f3bbcf404a628bf488b6aeb2d84a94c269afdb86f6e529343ab01f qtsvg-everywhere-src-$QT.tar.xz
8148408380ffea03101a26305c812b612ea30dbc07121e58707601522404d49b qttools-everywhere-src-$QT.tar.xz
8e49a2df88a12c376a479ae7bd272a91cf57ebb4e7c0cf7341b3565df99d2314 qttranslations-everywhere-src-$QT.tar.xz
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
a8e4a25e5c2686fd36981e527ed05e451fcfc226bddf350f4e76181371190937 shaderc-$SHADERC.tar.gz
9427deccbdf4bde6a269938df38c6bd75247493786a310d8d733a2c82065ef47 shaderc-glslang-$SHADERC_GLSLANG.tar.gz
@@ -237,9 +237,6 @@ cd "qtbase-everywhere-src-$QT"
# Patch Qt to support macOS 11
patch -p1 < "$SCRIPTDIR/qt-macos11compat.patch"
# Patch Qt to fix a bug with message boxes on Tahoe
patch -p1 < "$SCRIPTDIR/clickbutton.patch"
# since we don't have a direct reference to QtSvg, it doesn't deployed directly from the main binary
# (only indirectly from iconengines), and the libqsvg.dylib imageformat plugin does not get deployed.
# We could run macdeployqt twice, but that's even more janky than patching it.

View File

@@ -1,16 +0,0 @@
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
@@ -88,6 +88,11 @@ bool QCocoaMessageDialog::show(Qt::WindowModality windowModality)
qCWarning(lcQpaDialogs, "Cannot run window modal dialog without parent window");
return false;
}
+
+ // Tahoe has issues with window-modal alert buttons not responding to mouse
+ if (windowModality == Qt::WindowModal
+ && QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe)
+ return false;
// And without options we don't know what to show
if (!options())
# Source https://codereview.qt-project.org/c/qt/qtbase/+/689796

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

@@ -48,7 +48,7 @@ set LIBJPEGTURBO=3.1.2
set LIBPNG=1650
set LIBPNGLONG=1.6.50
set SDL=SDL3-3.2.26
set QT=6.10.0
set QT=6.10.1
set QTMINOR=6.10
set QTAPNG=1.3.0
set LZ4=1.10.0
@@ -72,11 +72,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error

View File

@@ -46,7 +46,7 @@ set LIBJPEGTURBO=3.1.2
set LIBPNG=1650
set SDL=SDL3-3.2.26
set LIBPNGLONG=1.6.50
set QT=6.10.0
set QT=6.10.1
set QTMINOR=6.10
set QTAPNG=1.3.0
set LZ4=1.10.0
@@ -70,11 +70,11 @@ call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" d3b5379edcace266273d789249b6d68ae9495ec1b0b562ba6d039034cd315d8e || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" ac2fe34a9f1c1451b6785474e9b1b64eb59edef6553be3d630240f16a730456d || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" c12f8bfb617e4a03da104be36f6966ba7f64bee331f0095da1a649a1149796d2 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c6d0f0a512304ad87b20f5ff604442dd8d55769d659576ecfe5462fcd7bb9b7d || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e6cc1ebf62cf37d81f3b86990086108518037bb383e75da327f297cc4fc1ae36 || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" c43f471a808b07fc541528410e94ce89c6745bdc1d744492e19911d35fbf7d33 || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2d828d8c999fdd18167937c071781c22321c643b04a106c714411c2356cdb26d || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" ddd74a417d2397eb085d047a9b6ba52b76e748055817f728fe691f8456035d23 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" db8e49ed50912c3c064a4f9ada7791c09eccec5a8d53463a19608eaab17679f0 || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" 868eb651e395d48ade5932ef2c386e606e054eb5888ebe5284fbd8cb63ed935a || goto error
call :downloadfile "QtApng-%QTAPNG%.zip" "https://github.com/jurplel/QtApng/archive/refs/tags/%QTAPNG%.zip" 5176082cdd468047a7eb1ec1f106b032f57df207aa318d559b29606b00d159ac || goto error
call :downloadfile "lz4-%LZ4%.zip" "https://github.com/lz4/lz4/archive/refs/tags/v%LZ4%.zip" 3224b4c80f351f194984526ef396f6079bd6332dd9825c72ac0d7a37b3cdc565 || goto error
call :downloadfile "zlib%ZLIBSHORT%.zip" "https://zlib.net/zlib%ZLIBSHORT%.zip" 72af66d44fcc14c22013b46b814d5d2514673dda3d115e64b690c1ad636e7b17 || goto error

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

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,
@@ -839,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,
@@ -1119,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,
@@ -1774,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

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

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

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

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

@@ -31,60 +31,15 @@ static constexpr int SIZE_HINT_HEIGHT_TITLES = SIZE_HINT_HEIGHT + COVER_ART_SPAC
static constexpr int MIN_COVER_CACHE_SIZE = 256;
static int DPRScale(const int size, const qreal dpr)
{
return static_cast<int>(static_cast<qreal>(size) * dpr);
}
static int DPRUnscale(const int size, const qreal dpr)
{
return static_cast<int>(static_cast<qreal>(size) / dpr);
}
static void resizeAndPadPixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr)
{
const int dpr_expected_width = DPRScale(expected_width, dpr);
const int dpr_expected_height = DPRScale(expected_height, dpr);
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
return;
*pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
return;
// QPainter works in unscaled coordinates.
int xoffs = 0;
int yoffs = 0;
if (pm->width() < dpr_expected_width)
xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr);
if (pm->height() < dpr_expected_height)
yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr);
QPixmap padded_image(dpr_expected_width, dpr_expected_height);
padded_image.setDevicePixelRatio(dpr);
padded_image.fill(Qt::transparent);
QPainter painter;
if (painter.begin(&padded_image))
{
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawPixmap(xoffs, yoffs, *pm);
painter.setCompositionMode(QPainter::CompositionMode_Destination);
painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
painter.end();
}
*pm = padded_image;
}
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, const int width, const int height,
const float scale, const qreal dpr, const std::string& title)
static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
qreal dpr, const std::string& title)
{
QPixmap pm(placeholder_pixmap.copy());
pm.setDevicePixelRatio(dpr);
if (pm.isNull())
return QPixmap();
resizeAndPadPixmap(&pm, width, height, dpr);
QtUtils::resizeAndScalePixmap(&pm, width, height, dpr, QtUtils::ScalingMode::Fit, 100);
QPainter painter;
if (painter.begin(&pm))
{
@@ -199,7 +154,7 @@ void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
else
{
image.setDevicePixelRatio(m_dpr);
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr);
QtUtils::resizeAndScalePixmap(&image, getCoverArtWidth(), getCoverArtHeight(), m_dpr, QtUtils::ScalingMode::Fit, 100);
}
}

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
@@ -234,6 +257,7 @@ 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);
@@ -251,18 +275,28 @@ void GameListWidget::initialize()
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);
@@ -310,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;
@@ -398,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());
@@ -406,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
@@ -554,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();
});
}
@@ -708,7 +718,7 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
QWidget::resizeEvent(event);
resizeTableViewColumnsToFit();
m_model->updateCacheSize(width(), height());
setCustomBackground();
processBackgroundFrames();
}
bool GameListWidget::event(QEvent* event)
@@ -726,91 +736,100 @@ bool GameListWidget::event(QEvent* event)
void GameListWidget::resizeTableViewColumnsToFit()
{
QtUtils::ResizeColumnsForTableView(m_table_view, {
55, // type
85, // code
-1, // title
-1, // file title
75, // crc
95, // time played
90, // 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)));
}
void GameListWidget::loadTableViewColumnVisibilitySettings()
{
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
}};
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();
Host::SetBaseBoolSettingValue("GameListTableView", "SortDescending", static_cast<bool>(sort_order));
}
std::optional<GameList::Entry> GameListWidget::getSelectedEntry() const

View File

@@ -3,6 +3,7 @@
#pragma once
#include "QtUtils.h"
#include "ui_EmptyGameListWidget.h"
#include "ui_GameListWidget.h"
@@ -49,9 +50,9 @@ public:
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;
@@ -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

@@ -1099,7 +1099,7 @@ bool MainWindow::shouldMouseLock() const
if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false))
return false;
if (m_display_created == false || m_display_surface == nullptr && !isRenderingToMain())
if (m_display_created == false || m_display_surface == nullptr)
return false;
bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) &&
@@ -2502,9 +2502,6 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
if (!isRenderingFullscreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
// Detatch display surface from container.
m_display_surface->setParent(nullptr);
if (isRenderingToMain())
{
pxAssertRel(m_ui.mainContainer->indexOf(m_display_container) == 1, "Display widget in stack");
@@ -2516,17 +2513,12 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
}
}
if (m_display_surface)
{
m_display_surface->destroy();
m_display_surface = 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);
}
@@ -2578,11 +2570,21 @@ void MainWindow::checkMousePosition(int x, int y)
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->devicePixelRatio();
const QRectF physicalBounds(
logicalPosition.x() * scale,
logicalPosition.y() * scale,
screenPosition.x() + (logicalPosition.x() - screenPosition.x()) * scale,
screenPosition.y() + (logicalPosition.y() - screenPosition.y()) * scale,
logicalSize.width() * scale,
logicalSize.height() * scale);
@@ -2638,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();
});

View File

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

View File

@@ -10,6 +10,7 @@
#include <QtGui/QAction>
#include <QtGui/QDesktopServices>
#include <QtGui/QKeyEvent>
#include <QtGui/QPainter>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QHeaderView>
@@ -135,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)

View File

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

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

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

@@ -35,7 +35,7 @@
cdvdStruct cdvd;
s64 PSXCLK = 36864000;
u32 PSXCLK = 36864000;
static constexpr s32 GMT9_OFFSET_SECONDS = 9 * 60 * 60; // 32400

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

@@ -713,7 +713,7 @@ struct Pcsx2Config
union
{
u64 bitset[2];
u64 bitsets[2];
struct
{

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

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

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

@@ -674,11 +674,11 @@ void GSgetStats(SmallStringBase& info)
}
else if (pps >= _1kb)
{
pps /= _1kb; // Kpps
prefix = 'K';
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),
@@ -693,7 +693,7 @@ void GSgetStats(SmallStringBase& info)
}
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),
@@ -733,7 +733,7 @@ void GSgetMemoryStats(SmallStringBase& info)
{
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 | T: {} MB | S: {} MB | H: {} MB | P: {} 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),
@@ -743,7 +743,7 @@ void GSgetMemoryStats(SmallStringBase& info)
else
{
const double total_MB = targets_MB + sources_MB + pool_MB;
info.format("VRAM: {} MB | T: {} MB | S: {} MB | P: {} MB",
info.format("VRAM: {} MB | TGT: {} MB | SRC: {} MB | PL: {} MB",
format_precision(total_MB),
format_precision(targets_MB),
format_precision(sources_MB),

View File

@@ -36,7 +36,7 @@ public:
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;
@@ -46,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; }
@@ -68,4 +68,4 @@ public:
void Dump(const std::string& filename, bool hw);
};
extern GSPerfMon g_perfmon;
extern GSPerfMon g_perfmon;

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

@@ -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)
@@ -1477,7 +1467,7 @@ void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
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)
@@ -1596,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);
}
@@ -1607,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);
}
}
@@ -1621,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)
@@ -1647,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])
@@ -1659,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)
@@ -2158,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
@@ -2179,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);
@@ -2447,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;
@@ -2624,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)
{
@@ -2634,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)
{
@@ -2697,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)
{
@@ -2714,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;
@@ -2766,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,
@@ -1639,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();
@@ -3831,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);
@@ -3940,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);
@@ -3964,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

@@ -26,7 +26,7 @@ GSRendererHW::GSRendererHW()
// Hope nothing requires too many draw calls.
m_drawlist.reserve(2048);
memset(&m_conf, 0, sizeof(m_conf));
memset(static_cast<void*>(&m_conf), 0, sizeof(m_conf));
ResetStates();
}
@@ -5769,9 +5769,6 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
m_conf.ps.a_masked = 1;
m_conf.ps.blend_c = 0;
m_conf.require_one_barrier |= true;
// Alpha write is masked, 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.
DATE_BARRIER = DATE;
}
else
blend_ad_alpha_masked = false;
@@ -7251,7 +7248,7 @@ void GSRendererHW::ResetStates()
{
// We don't want to zero out the constant buffers, since fields used by the current draw could result in redundant uploads.
// This memset should be pretty efficient - the struct is 16 byte aligned, as is the cb_vs offset.
memset(&m_conf, 0, reinterpret_cast<const char*>(&m_conf.cb_vs) - reinterpret_cast<const char*>(&m_conf));
memset(static_cast<void*>(&m_conf), 0, reinterpret_cast<const char*>(&m_conf.cb_vs) - reinterpret_cast<const char*>(&m_conf));
}
__ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm)
@@ -7698,6 +7695,11 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
}
// Always swap DATE with DATE_BARRIER if we have barriers on when alpha write is masked.
// This is always enabled on vk/gl but not on dx11/12 as copies are slow so we can selectively enable it like now.
if (DATE && !m_conf.colormask.wa && (m_conf.require_one_barrier || m_conf.require_full_barrier))
DATE_BARRIER = true;
if ((m_conf.ps.tex_is_fb && rt && rt->m_rt_alpha_scale) || (tex && tex->m_from_target && tex->m_target_direct && tex->m_from_target->m_rt_alpha_scale))
m_conf.ps.rta_source_correction = 1;
@@ -7937,7 +7939,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
}
else if (DATE_one)
{
if (features.texture_barrier)
const bool multidraw_fb_copy = features.multidraw_fb_copy && (m_conf.require_one_barrier || m_conf.require_full_barrier);
if (features.texture_barrier || multidraw_fb_copy)
{
m_conf.require_one_barrier = true;
m_conf.ps.date = 5 + m_cached_ctx.TEST.DATM;
@@ -9430,8 +9433,8 @@ GSHWDrawConfig& GSRendererHW::BeginHLEHardwareDraw(
// Bit gross, but really no other way to ensure there's nothing of the last draw left over.
GSHWDrawConfig& config = m_conf;
std::memset(&config.cb_vs, 0, sizeof(config.cb_vs));
std::memset(&config.cb_ps, 0, sizeof(config.cb_ps));
std::memset(static_cast<void*>(&config.cb_vs), 0, sizeof(config.cb_vs));
std::memset(static_cast<void*>(&config.cb_ps), 0, sizeof(config.cb_ps));
// Reused between draws, since the draw config is shared, you can't have multiple draws in flight anyway.
static GSVertex vertices[4];
@@ -9564,4 +9567,4 @@ std::size_t GSRendererHW::ComputeDrawlistGetSize(float scale)
GetPrimitiveOverlapDrawlist(true, save_bbox, scale);
}
return m_drawlist.size();
}
}

View File

@@ -2663,8 +2663,10 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
for (auto i = list.begin(); i != list.end(); ++i)
{
Target* t = *i;
const bool half_buffer_match = GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets && TEX0.TBW == t->m_TEX0.TBW && TEX0.PSM == t->m_TEX0.PSM &&
bp == GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, GSVector4i(0, size.y, size.x, size.y + 1));
// Make sure the target is inside the texture
if (t->m_TEX0.TBP0 <= bp && bp <= t->m_end_block && t->Inside(bp, TEX0.TBW, TEX0.PSM, GSVector4i::loadh(size)))
if (t->m_TEX0.TBP0 <= bp && bp <= t->m_end_block && (half_buffer_match || t->Inside(bp, TEX0.TBW, TEX0.PSM, GSVector4i::loadh(size))))
{
if (dst && (GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw))
continue;

View File

@@ -407,8 +407,6 @@ public:
void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2& ds);
/// Copy from a position in sTex to the same position in the currently active render encoder using the given fs pipeline and rect
void RenderCopy(GSTexture* sTex, id<MTLRenderPipelineState> pipeline, const GSVector4i& rect);
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader = ShaderConvert::COPY, bool linear = true) override;
void StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader = ShaderConvert::COPY) override;
void PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, PresentShader shader, float shaderTime, bool linear) override;
void DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_rects, GSTexture* dTex, ShaderConvert shader) override;
void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override;
@@ -452,6 +450,10 @@ public:
void RenderImGui(ImDrawData* data);
u32 FrameNo() const { return m_frame; }
protected:
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
};
static constexpr bool IsCommandBufferCompleted(MTLCommandBufferStatus status)

View File

@@ -1600,33 +1600,21 @@ void GSDeviceMTL::RenderCopy(GSTexture* sTex, id<MTLRenderPipelineState> pipelin
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
}
void GSDeviceMTL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader, bool linear)
void GSDeviceMTL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear)
{ @autoreleasepool {
pxAssert(linear ? SupportsBilinear(shader) : SupportsNearest(shader));
id<MTLRenderPipelineState> pipeline = m_convert_pipeline[static_cast<int>(shader)];
const LoadAction load_action = (cms.wrgba == 0xf) ? LoadAction::DontCareIfFull : LoadAction::Load;
id<MTLRenderPipelineState> pipeline;
if (HasVariableWriteMask(shader))
pipeline = m_convert_pipeline_copy_mask[GetShaderIndexForMask(shader, cms.wrgba)];
else
pipeline = m_convert_pipeline[static_cast<int>(shader)];
pxAssertRel(pipeline, fmt::format("No pipeline for {}", shaderName(shader)).c_str());
const LoadAction load_action = (ShaderConvertWriteMask(shader) == 0xf) ? LoadAction::DontCareIfFull : LoadAction::Load;
DoStretchRect(sTex, sRect, dTex, dRect, pipeline, linear, load_action, nullptr, 0);
}}
void GSDeviceMTL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader)
{ @autoreleasepool {
int sel = 0;
if (red) sel |= 1;
if (green) sel |= 2;
if (blue) sel |= 4;
if (alpha) sel |= 8;
if (shader == ShaderConvert::RTA_CORRECTION) sel |= 16;
const int color_sel = sel & 15;
id<MTLRenderPipelineState> pipeline = m_convert_pipeline_copy_mask[sel];
DoStretchRect(sTex, sRect, dTex, dRect, pipeline, false, color_sel == 15 ? LoadAction::DontCareIfFull : LoadAction::Load, nullptr, 0);
}}
static_assert(sizeof(DisplayConstantBuffer) == sizeof(GSMTLPresentPSUniform));
static_assert(offsetof(DisplayConstantBuffer, SourceRect) == offsetof(GSMTLPresentPSUniform, source_rect));
static_assert(offsetof(DisplayConstantBuffer, TargetRect) == offsetof(GSMTLPresentPSUniform, target_rect));
@@ -1683,9 +1671,9 @@ void GSDeviceMTL::DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_r
const u32 end = i * 4;
const u32 vertex_count = end - start;
const u32 index_count = vertex_count + (vertex_count >> 1); // 6 indices per 4 vertices
const int rta_bit = shader == ShaderConvert::RTA_CORRECTION ? 16 : 0;
pxAssert(HasVariableWriteMask(shader) || wmask == 0xf);
id<MTLRenderPipelineState> new_pipeline = wmask == 0xf ? m_convert_pipeline[static_cast<int>(shader)]
: m_convert_pipeline_copy_mask[wmask | rta_bit];
: m_convert_pipeline_copy_mask[GetShaderIndexForMask(shader, wmask)];
if (new_pipeline != pipeline)
{
pipeline = new_pipeline;

View File

@@ -272,8 +272,8 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_index_stream_buffer->Bind();
// Force UBOs to be uploaded on first use.
std::memset(&m_vs_cb_cache, 0xFF, sizeof(m_vs_cb_cache));
std::memset(&m_ps_cb_cache, 0xFF, sizeof(m_ps_cb_cache));
std::memset(static_cast<void*>(&m_vs_cb_cache), 0xFF, sizeof(m_vs_cb_cache));
std::memset(static_cast<void*>(&m_ps_cb_cache), 0xFF, sizeof(m_ps_cb_cache));
static_assert(sizeof(GSVertexPT1) == sizeof(GSVertex), "wrong GSVertex size");
for (u32 i = 0; i < 8; i++)
@@ -1253,7 +1253,7 @@ GSTexture* GSDeviceOGL::InitPrimDateTexture(GSTexture* rt, const GSVector4i& are
return nullptr;
GL_PUSH("PrimID Destination Alpha Clear");
StretchRect(rt, GSVector4(area) / GSVector4(rtsize).xyxy(), tex, GSVector4(area), m_date.primid_ps[static_cast<u8>(datm)], false);
DoStretchRect(rt, GSVector4(area) / GSVector4(rtsize).xyxy(), tex, GSVector4(area), m_date.primid_ps[static_cast<u8>(datm)], false);
return tex;
}
@@ -1472,31 +1472,20 @@ void GSDeviceOGL::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r
dTex->SetState(GSTexture::State::Dirty);
}
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderConvert shader, bool linear)
void GSDeviceOGL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
OMColorMaskSelector cms, ShaderConvert shader, bool linear)
{
pxAssert(dTex->IsDepthStencil() == HasDepthOutput(shader));
pxAssert(linear ? SupportsBilinear(shader) : SupportsNearest(shader));
StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[(int)shader], false, OMColorMaskSelector(ShaderConvertWriteMask(shader)), linear);
DoStretchRect(sTex, sRect, dTex, dRect, m_convert.ps[static_cast<int>(shader)], false, cms, linear);
}
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool linear)
void GSDeviceOGL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
const GLProgram& ps, bool linear)
{
StretchRect(sTex, sRect, dTex, dRect, ps, false, OMColorMaskSelector(), linear);
DoStretchRect(sTex, sRect, dTex, dRect, ps, false, OMColorMaskSelector(), linear);
}
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, bool red, bool green, bool blue, bool alpha, ShaderConvert shader)
{
OMColorMaskSelector cms;
cms.wr = red;
cms.wg = green;
cms.wb = blue;
cms.wa = alpha;
StretchRect(sTex, sRect, dTex, dRect, m_convert.ps[(int)shader], false, cms, false);
}
void GSDeviceOGL::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear)
void GSDeviceOGL::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear)
{
CommitClear(sTex, true);
@@ -1816,12 +1805,12 @@ void GSDeviceOGL::DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex,
// Blend with a constant alpha
m_merge_obj.ps[1].Bind();
m_merge_obj.ps[1].Uniform4fv(0, GSVector4::unorm8(c).v);
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector(), linear);
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[1], true, OMColorMaskSelector(), linear);
}
else
{
// Blend with 2 * input alpha
StretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector(), linear);
DoStretchRect(sTex[0], sRect[0], dTex, dRect[0], m_merge_obj.ps[0], true, OMColorMaskSelector(), linear);
}
}
@@ -1836,7 +1825,7 @@ void GSDeviceOGL::DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture
m_interlace.ps[static_cast<int>(shader)].Bind();
m_interlace.ps[static_cast<int>(shader)].Uniform4fv(0, cb.ZrH.F32);
StretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast<int>(shader)], linear);
DoStretchRect(sTex, sRect, dTex, dRect, m_interlace.ps[static_cast<int>(shader)], linear);
}
bool GSDeviceOGL::CompileFXAAProgram()
@@ -1875,7 +1864,7 @@ void GSDeviceOGL::DoFXAA(GSTexture* sTex, GSTexture* dTex)
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, s.x, s.y);
StretchRect(sTex, sRect, dTex, dRect, m_fxaa.ps, true);
DoStretchRect(sTex, sRect, dTex, dRect, m_fxaa.ps, true);
}
bool GSDeviceOGL::CompileShadeBoostProgram()
@@ -1909,10 +1898,10 @@ void GSDeviceOGL::DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float par
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, s.x, s.y);
StretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps, false);
DoStretchRect(sTex, sRect, dTex, dRect, m_shadeboost.ps, false);
}
void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, SetDATM datm)
void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox)
{
GL_PUSH("DATE First Pass");
@@ -1933,6 +1922,17 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* ver
// ia
const GSVector4 src = GSVector4(bbox) / GSVector4(ds->GetSize()).xyxy();
const GSVector4 dst = src * 2.f - 1.f;
const GSVertexPT1 vertices[] =
{
{GSVector4(dst.x, dst.y, 0.0f, 0.0f), GSVector2(src.x, src.y)},
{GSVector4(dst.z, dst.y, 0.0f, 0.0f), GSVector2(src.z, src.y)},
{GSVector4(dst.x, dst.w, 0.0f, 0.0f), GSVector2(src.x, src.w)},
{GSVector4(dst.z, dst.w, 0.0f, 0.0f), GSVector2(src.z, src.w)},
};
IASetVAO(m_vao);
IASetVertexBuffer(vertices, 4);
IASetPrimitiveTopology(GL_TRIANGLE_STRIP);
@@ -2490,18 +2490,8 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
}
[[fallthrough]];
case GSHWDrawConfig::DestinationAlphaMode::Stencil:
{
const GSVector4 src = GSVector4(config.drawarea) / GSVector4(config.ds->GetSize()).xyxy();
const GSVector4 dst = src * 2.f - 1.f;
GSVertexPT1 vertices[] =
{
{GSVector4(dst.x, dst.y, 0.0f, 0.0f), GSVector2(src.x, src.y)},
{GSVector4(dst.z, dst.y, 0.0f, 0.0f), GSVector2(src.z, src.y)},
{GSVector4(dst.x, dst.w, 0.0f, 0.0f), GSVector2(src.x, src.w)},
{GSVector4(dst.z, dst.w, 0.0f, 0.0f), GSVector2(src.z, src.w)},
};
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, vertices, config.datm);
}
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, config.datm, config.drawarea);
break;
}
GSTexture* draw_rt_clone = nullptr;

View File

@@ -265,6 +265,10 @@ private:
void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds);
protected:
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
public:
GSDeviceOGL();
virtual ~GSDeviceOGL();
@@ -317,10 +321,8 @@ public:
// BlitRect *does* mess with GL state, be sure to re-bind.
void BlitRect(GSTexture* sTex, const GSVector4i& r, const GSVector2i& dsize, bool at_origin, bool linear);
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, const GLProgram& ps, 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, const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, bool linear = true);
void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool linear);
void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, const GLProgram& ps, bool alpha_blend, OMColorMaskSelector cms, 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;
@@ -332,7 +334,7 @@ public:
void RenderHW(GSHWDrawConfig& config) override;
void SendHWDraw(const GSHWDrawConfig& config, bool one_barrier, bool full_barrier);
void SetupDATE(GSTexture* rt, GSTexture* ds, const GSVertexPT1* vertices, SetDATM datm);
void SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox);
void IASetVAO(GLuint vao);
void IASetPrimitiveTopology(GLenum topology);

View File

@@ -1549,7 +1549,7 @@ void GSRasterizerList::Queue(const GSRingHeap::SharedPtr<GSRasterizerData>& data
pxAssert(r.top >= 0 && r.top < 2048 && r.bottom >= 0 && r.bottom < 2048);
int top = r.top >> m_thread_height;
int bottom = std::min<int>((r.bottom + (1 << m_thread_height) - 1) >> m_thread_height, top + m_workers.size());
int bottom = std::min<int>((r.bottom + (1 << m_thread_height) - 1) >> m_thread_height, top + (int)m_workers.size());
while (top < bottom)
{

View File

@@ -1652,7 +1652,7 @@ void GSRendererSW::SharedData::UpdateSource()
std::string s;
for (size_t i = 0; m_tex[i].t; i++)
for (u32 i = 0; m_tex[i].t; i++)
{
const GIFRegTEX0& TEX0 = g_gs_renderer->GetTex0Layer(i);

View File

@@ -2821,31 +2821,16 @@ void GSDeviceVK::CopyRect(GSTexture* sTex, GSTexture* dTex, const GSVector4i& r,
dTexVK->SetState(GSTexture::State::Dirty);
}
void GSDeviceVK::StretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
ShaderConvert shader /* = ShaderConvert::COPY */, bool linear /* = true */)
void GSDeviceVK::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));
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(static_cast<GSTextureVK*>(sTex), sRect, static_cast<GSTextureVK*>(dTex), dRect,
dTex ? m_convert[static_cast<int>(shader)] : m_present[static_cast<int>(shader)], linear,
ShaderConvertWriteMask(shader) == 0xf);
}
void GSDeviceVK::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);
const bool allow_discard = (index == 0xf);
int rta_offset = (shader == ShaderConvert::RTA_CORRECTION) ? 16 : 0;
DoStretchRect(static_cast<GSTextureVK*>(sTex), sRect, static_cast<GSTextureVK*>(dTex), dRect, m_color_copy[index + rta_offset],
false, allow_discard);
const bool allow_discard = (cms.wrgba == 0xf);
VkPipeline state;
if (HasVariableWriteMask(shader))
state = m_color_copy[GetShaderIndexForMask(shader, cms.wrgba)];
else
state = dTex ? m_convert[static_cast<int>(shader)] : m_present[static_cast<int>(shader)];
DoStretchRect(static_cast<GSTextureVK*>(sTex), sRect, static_cast<GSTextureVK*>(dTex), dRect, state, linear, allow_discard);
}
void GSDeviceVK::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
@@ -2967,10 +2952,10 @@ void GSDeviceVK::DoMultiStretchRects(
BeginRenderPassForStretchRect(dTex, rc, rc, false);
SetUtilityTexture(rects[0].src, rects[0].linear ? m_linear_sampler : m_point_sampler);
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] : m_convert[static_cast<int>(shader)]);
pxAssert(HasVariableWriteMask(shader) || rects[0].wmask.wrgba == 0xf);
SetPipeline((rects[0].wmask.wrgba != 0xf) ?
m_color_copy[GetShaderIndexForMask(shader, rects[0].wmask.wrgba)] :
m_convert[static_cast<int>(shader)]);
if (ApplyUtilityState())
DrawIndexedPrimitive();

View File

@@ -278,7 +278,6 @@ private:
u32 m_current_frame = 0;
bool m_last_submit_failed = false;
bool m_last_present_failed = false;
std::map<u32, VkRenderPass> m_render_pass_cache;
@@ -471,6 +470,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:
GSDeviceVK();
~GSDeviceVK() override;
@@ -526,10 +529,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 DrawMultiStretchRects(

View File

@@ -213,16 +213,18 @@ static void GSDumpReplayerLoadInitialState()
Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to load GS state.");
}
static void GSDumpReplayerSendPacketToMTGS(GIF_PATH path, const u8* data, u32 length)
static void GSDumpReplayerSendPacketToMTGS(GIF_PATH path, const u8* data, size_t length)
{
pxAssert((length % 16) == 0);
pxAssert((length % 16) == 0 && length < UINT32_MAX);
const u32 truncated_length = static_cast<u32>(length);
Gif_Path& gifPath = gifUnit.gifPath[path];
gifPath.CopyGSPacketData(const_cast<u8*>(data), length);
gifPath.CopyGSPacketData(const_cast<u8*>(data), truncated_length);
GS_Packet gsPack;
gsPack.offset = gifPath.curOffset;
gsPack.size = length;
gsPack.size = truncated_length;
gifPath.curOffset += length;
Gif_AddCompletedGSPacket(gsPack, path);
}
@@ -248,7 +250,7 @@ static void GSDumpReplayerFrameLimit()
const s64 ms = GetTickFrequency() / 1000;
const s64 sleep = s_next_frame_time - now - ms;
if (sleep > ms)
Threading::Sleep(sleep / ms);
Threading::Sleep(static_cast<s32>(sleep / ms));
while ((now = GetCPUTicks()) < s_next_frame_time)
ShortSpin();
s_next_frame_time = std::max(now, s_next_frame_time + s_frame_ticks);
@@ -284,8 +286,13 @@ void GSDumpReplayerCpuStep()
{
case GSDumpTypes::GSTransferPath::Path1Old:
{
if(packet.length > 16384)
{
Console.Error("GSDumpReplayer: Path1Old transfer exceeds 16KB buffer. Skipping transfer");
break;
}
std::unique_ptr<u8[]> data(new u8[16384]);
const s32 addr = 16384 - packet.length;
const size_t addr = 16384 - packet.length;
std::memcpy(data.get(), packet.data + addr, packet.length);
GSDumpReplayerSendPacketToMTGS(GIF_PATH_1, data.get(), packet.length);
}
@@ -332,7 +339,7 @@ void GSDumpReplayerCpuStep()
case GSDumpTypes::GSType::Registers:
{
std::memcpy(PS2MEM_GS, packet.data, std::min<s32>(packet.length, Ps2MemSize::GSregs));
std::memcpy(PS2MEM_GS, packet.data, std::min<s32>(static_cast<u32>(packet.length), Ps2MemSize::GSregs));
}
break;
}

View File

@@ -521,7 +521,7 @@ struct Gif_Path
// GS Packets that MTGS hasn't yet processed
u32 GetPendingGSPackets()
{
return mtvu.gsPackQueue.size();
return (u32)mtvu.gsPackQueue.size();
}
};

View File

@@ -71,7 +71,7 @@ std::pair<const char*, u32> Host::LookupTranslationString(const std::string_view
add_string:
s_translation_string_mutex.unlock_shared();
s_translation_string_mutex.lock();
std::lock_guard lock(s_translation_string_mutex);
if (s_translation_string_cache.empty()) [[unlikely]]
{
@@ -110,7 +110,6 @@ add_string:
ret.first = &s_translation_string_cache[insert_pos];
ret.second = static_cast<u32>(len);
s_translation_string_mutex.unlock();
return ret;
}
@@ -132,10 +131,9 @@ std::string Host::TranslateToString(const std::string_view context, const std::s
void Host::ClearTranslationCache()
{
s_translation_string_mutex.lock();
std::lock_guard lock(s_translation_string_mutex);
s_translation_string_map.clear();
s_translation_string_cache_pos = 0;
s_translation_string_mutex.unlock();
}
void Host::ReportFormattedInfoAsync(const std::string_view title, const char* format, ...)

View File

@@ -312,7 +312,7 @@ std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebDriverName
names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"));
auto cubeb_names = cubeb_get_backend_names();
for (int i = 0; i < cubeb_names.count; i++)
for (size_t i = 0; i < cubeb_names.count; i++)
names.emplace_back(cubeb_names.names[i], cubeb_names.names[i]);
return names;

View File

@@ -488,6 +488,7 @@ namespace FullscreenUI
static ImGuiFullscreen::FileSelectorFilters GetOpenFileFilters();
static ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters();
static ImGuiFullscreen::FileSelectorFilters GetAudioFileFilters();
static ImGuiFullscreen::FileSelectorFilters GetImageFileFilters();
static void DoStartPath(
const std::string& path, std::optional<s32> state_index = std::nullopt, std::optional<bool> fast_boot = std::nullopt);
static void DoStartFile();
@@ -701,6 +702,16 @@ namespace FullscreenUI
static std::vector<const GameList::Entry*> s_game_list_sorted_entries;
static GameListView s_game_list_view = GameListView::Grid;
//////////////////////////////////////////////////////////////////////////
// Background
//////////////////////////////////////////////////////////////////////////
static void LoadCustomBackground();
static void DrawCustomBackground();
static std::shared_ptr<GSTexture> s_custom_background_texture;
static std::string s_custom_background_path;
static bool s_custom_background_enabled = false;
//////////////////////////////////////////////////////////////////////////
// Achievements
//////////////////////////////////////////////////////////////////////////
@@ -980,6 +991,8 @@ bool FullscreenUI::Initialize()
s_hotkey_list_cache = InputManager::GetHotkeyList();
MTGS::SetRunIdle(true);
LoadCustomBackground();
if (VMManager::HasValidVM())
{
UpdateGameDetails(VMManager::GetDiscPath(), VMManager::GetDiscSerial(), VMManager::GetTitle(true), VMManager::GetDiscCRC(),
@@ -1025,6 +1038,8 @@ void FullscreenUI::CheckForConfigChanges(const Pcsx2Config& old_config)
ImGuiFullscreen::SetTheme(Host::GetBaseStringSettingValue("UI", "FullscreenUITheme", "Dark"));
LoadCustomBackground();
// If achievements got disabled, we might have the menu open...
// That means we're going to be reaching achievement state.
if (old_config.Achievements.Enabled && !EmuConfig.Achievements.Enabled)
@@ -1184,6 +1199,11 @@ void FullscreenUI::Shutdown(bool clear_state)
s_about_window_open = false;
}
s_hotkey_list_cache = {};
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
DestroyResources();
ImGuiFullscreen::Shutdown(clear_state);
s_initialized = false;
@@ -1195,6 +1215,15 @@ void FullscreenUI::Render()
if (!s_initialized)
return;
// see if background setting changed
static std::string s_last_background_path;
std::string current_path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
if (s_last_background_path != current_path)
{
s_last_background_path = current_path;
LoadCustomBackground();
}
for (std::unique_ptr<GSTexture>& tex : s_cleanup_textures)
g_gs_device->Recycle(tex.release());
s_cleanup_textures.clear();
@@ -1202,6 +1231,20 @@ void FullscreenUI::Render()
ImGuiFullscreen::BeginLayout();
const bool should_draw_background = (s_current_main_window == MainWindowType::Landing ||
s_current_main_window == MainWindowType::StartGame ||
s_current_main_window == MainWindowType::Exit ||
s_current_main_window == MainWindowType::GameList ||
s_current_main_window == MainWindowType::GameListSettings ||
s_current_main_window == MainWindowType::Settings) && s_custom_background_enabled && s_custom_background_texture;
ImVec4 original_background_color;
if (should_draw_background)
{
original_background_color = ImGuiFullscreen::UIBackgroundColor;
DrawCustomBackground();
}
// Primed achievements must come first, because we don't want the pause screen to be behind them.
if (s_current_main_window == MainWindowType::None && (EmuConfig.Achievements.Overlays || EmuConfig.Achievements.LBOverlays))
Achievements::DrawGameOverlays();
@@ -1297,6 +1340,9 @@ void FullscreenUI::Render()
s_game_settings_changed.store(false, std::memory_order_release);
}
if (should_draw_background)
ImGuiFullscreen::UIBackgroundColor = original_background_color;
ImGuiFullscreen::ResetCloseMenuIfNeeded();
}
@@ -1383,6 +1429,11 @@ ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetAudioFileFilters()
return {"*.wav"};
}
ImGuiFullscreen::FileSelectorFilters FullscreenUI::GetImageFileFilters()
{
return {"*.png", "*.jpg", "*.jpeg", "*.bmp"};
}
void FullscreenUI::DoStartPath(const std::string& path, std::optional<s32> state_index, std::optional<bool> fast_boot)
{
VMBootParameters params;
@@ -1598,6 +1649,158 @@ bool FullscreenUI::ShouldDefaultToGameList()
return Host::GetBaseBoolSettingValue("UI", "FullscreenUIDefaultToGameList", false);
}
//////////////////////////////////////////////////////////////////////////
// Custom Background
//////////////////////////////////////////////////////////////////////////
void FullscreenUI::LoadCustomBackground()
{
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
if (path.empty())
{
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
return;
}
if (s_custom_background_path == path && s_custom_background_texture)
{
s_custom_background_enabled = true;
return;
}
if (!Path::IsAbsolute(path))
path = Path::Combine(EmuFolders::DataRoot, path);
if (!FileSystem::FileExists(path.c_str()))
{
Console.Warning("Custom background file not found: %s", path.c_str());
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
return;
}
if (StringUtil::EndsWithNoCase(path, ".gif"))
{
Console.Warning("GIF files aren't supported as backgrounds: %s", path.c_str());
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
return;
}
if (StringUtil::EndsWithNoCase(path, ".webp"))
{
Console.Warning("WebP files aren't supported as backgrounds: %s", path.c_str());
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
return;
}
s_custom_background_texture = LoadTexture(path.c_str());
if (s_custom_background_texture)
{
s_custom_background_path = std::move(path);
s_custom_background_enabled = true;
}
else
{
Console.Error("Failed to load custom background: %s", path.c_str());
s_custom_background_path.clear();
s_custom_background_enabled = false;
}
}
void FullscreenUI::DrawCustomBackground()
{
if (!s_custom_background_enabled || !s_custom_background_texture)
return;
const ImGuiIO& io = ImGui::GetIO();
const ImVec2 display_size = io.DisplaySize;
const float opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f) / 100.0f;
const std::string mode = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", "fit");
const float tex_width = static_cast<float>(s_custom_background_texture->GetWidth());
const float tex_height = static_cast<float>(s_custom_background_texture->GetHeight());
ImVec2 img_min, img_max;
if (mode == "stretch")
{
// stretch to fill entire display (ignores aspect ratio)
img_min = ImVec2(0.0f, 0.0f);
img_max = display_size;
}
else if (mode == "fill")
{
// Fill display while preserving aspect ratio (could crop edges)
const float display_aspect = display_size.x / display_size.y;
const float tex_aspect = tex_width / tex_height;
float scale;
if (tex_aspect > display_aspect)
{
// Image is wider scale to height and crop sides
scale = display_size.y / tex_height;
}
else
{
// Image is taller scale to width and crop top/bottom
scale = display_size.x / tex_width;
}
const float scaled_width = tex_width * scale;
const float scaled_height = tex_height * scale;
const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y);
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height);
}
else // "fit" or default
{
// Fit on screen while preserving aspect ratio (no cropping)
const float display_aspect = display_size.x / display_size.y;
const float tex_aspect = tex_width / tex_height;
float scale;
if (tex_aspect > display_aspect)
{
// Image is wider than display
scale = display_size.x / tex_width;
}
else
{
// Image is taller than display
scale = display_size.y / tex_height;
}
const float scaled_width = tex_width * scale;
const float scaled_height = tex_height * scale;
const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y);
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height);
}
// Override the UIBackgroundColor that windows use
// We need to make windows transparent so our background image shows through
const ImVec4 transparent_bg = ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, 0.0f);
ImGuiFullscreen::UIBackgroundColor = transparent_bg;
ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
const ImU32 col = IM_COL32(255, 255, 255, static_cast<u8>(opacity * 255.0f));
bg_draw_list->AddImage(reinterpret_cast<ImTextureID>(s_custom_background_texture->GetNativeHandle()),
img_min, img_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
//////////////////////////////////////////////////////////////////////////
// Landing Window
//////////////////////////////////////////////////////////////////////////
@@ -3391,13 +3594,15 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const float bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f;
const bool using_custom_bg = s_custom_background_enabled && s_custom_background_texture;
const float header_bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f;
const float content_bg_alpha = using_custom_bg ? 0.0f : (VMManager::HasValidVM() ? 0.90f : 1.0f);
SettingsInterface* bsi = GetEditingSettingsInterface();
const bool game_settings = IsEditingGameSettings(bsi);
const bool show_advanced_settings = ShouldShowAdvancedSettings(bsi);
if (BeginFullscreenWindow(
ImVec2(0.0f, 0.0f), heading_size, "settings_category", ImVec4(UIPrimaryColor.x, UIPrimaryColor.y, UIPrimaryColor.z, bg_alpha)))
ImVec2(0.0f, 0.0f), heading_size, "settings_category", ImVec4(UIPrimaryColor.x, UIPrimaryColor.y, UIPrimaryColor.z, header_bg_alpha)))
{
static constexpr float ITEM_WIDTH = 25.0f;
@@ -3549,7 +3754,7 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(0.0f, heading_size.y),
ImVec2(io.DisplaySize.x, io.DisplaySize.y - heading_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT)),
TinyString::from_format("settings_page_{}", static_cast<u32>(s_settings_page)).c_str(),
ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, bg_alpha), 0.0f,
ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, content_bg_alpha), 0.0f,
ImVec2(ImGuiFullscreen::LAYOUT_MENU_WINDOW_X_PADDING, 0.0f)))
{
ResetFocusHere();
@@ -3810,6 +4015,72 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_CSTR("Show a save state selector UI when switching slots instead of showing a notification bubble."),
"EmuCore", "UseSavestateSelector", true);
MenuHeading(FSUI_CSTR("Background"));
std::string background_path = bsi->GetStringValue("UI", "GameListBackgroundPath", "");
const bool background_enabled = bsi->GetBoolValue("UI", "GameListBackgroundEnabled", false);
std::string background_display = FSUI_STR("None");
if (!background_path.empty() && background_enabled)
{
background_display = Path::GetFileName(background_path);
}
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_IMAGE, "Background Image"),
FSUI_CSTR("Select a custom background image to use in Big Picture Mode menus."),
background_display.c_str()))
{
OpenFileSelector(FSUI_ICONSTR(ICON_FA_IMAGE, "Select Background Image"), false,
[](const std::string& path) {
if (!path.empty())
{
auto lock = Host::GetSettingsLock();
SettingsInterface* bsi = GetEditingSettingsInterface(false);
std::string relative_path = Path::MakeRelative(path, EmuFolders::DataRoot);
bsi->SetStringValue("UI", "GameListBackgroundPath", relative_path.c_str());
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", true);
SetSettingsChanged(bsi);
Host::RunOnCPUThread([]() {
LoadCustomBackground();
});
}
CloseFileSelector();
},
GetImageFileFilters());
}
if (MenuButton(FSUI_ICONSTR(ICON_FA_XMARK, "Clear Background Image"),
FSUI_CSTR("Removes the custom background image.")))
{
bsi->DeleteValue("UI", "GameListBackgroundPath");
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", false);
SetSettingsChanged(bsi);
s_custom_background_texture.reset();
s_custom_background_path.clear();
s_custom_background_enabled = false;
}
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_DROPLET, "Background Opacity"),
FSUI_CSTR("Sets the transparency of the custom background image."),
"UI", "GameListBackgroundOpacity", 100, 0, 100, "%d%%");
static constexpr const char* s_background_mode_names[] = {
FSUI_NSTR("Fit"),
FSUI_NSTR("Fill"),
FSUI_NSTR("Stretch"),
};
static constexpr const char* s_background_mode_values[] = {
"fit",
"fill",
"stretch",
};
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Background Mode"),
FSUI_CSTR("Select how to display the background image."),
"UI", "GameListBackgroundMode", "fit", s_background_mode_names, s_background_mode_values, std::size(s_background_mode_names), true);
MenuHeading(FSUI_CSTR("Behaviour"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_SNOOZE, "Inhibit Screensaver"),
FSUI_CSTR("Prevents the screen saver from activating and the host from sleeping while emulation is running."), "EmuCore",
@@ -6595,6 +6866,46 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
BeginMenuButtons(submenu_item_count[static_cast<u32>(s_current_pause_submenu)], 1.0f, ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING,
ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
if (!ImGui::IsPopupOpen(0u, ImGuiPopupFlags_AnyPopup))
{
const bool up_pressed = ImGui::IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) ||
ImGui::IsKeyPressed(ImGuiKey_UpArrow, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
const bool down_pressed = ImGui::IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) ||
ImGui::IsKeyPressed(ImGuiKey_DownArrow, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
if (up_pressed || down_pressed)
{
const ImGuiID current_focus_id = ImGui::GetFocusID();
ImGuiWindow* window = ImGui::GetCurrentWindow();
ImGuiID first_id = 0;
ImGuiID last_id = 0;
switch (s_current_pause_submenu)
{
case PauseSubMenu::None:
first_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_PLAY, "Resume Game"));
last_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_POWER_OFF, "Close Game"));
break;
case PauseSubMenu::Exit:
first_id = ImGui::GetID(FSUI_ICONSTR(ICON_PF_BACKWARD, "Back To Pause Menu"));
last_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_POWER_OFF, "Exit Without Saving"));
break;
case PauseSubMenu::Achievements:
first_id = ImGui::GetID(FSUI_ICONSTR(ICON_PF_BACKWARD, "Back To Pause Menu"));
last_id = ImGui::GetID(FSUI_ICONSTR(ICON_FA_STOPWATCH, "Leaderboards"));
break;
}
if (first_id != 0 && last_id != 0)
{
if (up_pressed && current_focus_id == first_id)
ImGui::SetFocusID(last_id, window);
else if (down_pressed && current_focus_id == last_id)
ImGui::SetFocusID(first_id, window);
}
}
}
switch (s_current_pause_submenu)
{
case PauseSubMenu::None:
@@ -7554,9 +7865,12 @@ void FullscreenUI::DrawGameListWindow()
void FullscreenUI::DrawGameList(const ImVec2& heading_size)
{
ImGui::PushStyleColor(ImGuiCol_WindowBg, UIBackgroundColor);
if (!BeginFullscreenColumns(nullptr, heading_size.y, true, true))
{
EndFullscreenColumns();
ImGui::PopStyleColor();
return;
}
@@ -7753,6 +8067,8 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
}
EndFullscreenColumnWindow();
EndFullscreenColumns();
ImGui::PopStyleColor();
}
void FullscreenUI::DrawGameGrid(const ImVec2& heading_size)
@@ -8884,8 +9200,8 @@ TRANSLATE_NOOP("FullscreenUI", "Game type copied to clipboard.");
TRANSLATE_NOOP("FullscreenUI", "Game region copied to clipboard.");
TRANSLATE_NOOP("FullscreenUI", "Game compatibility copied to clipboard.");
TRANSLATE_NOOP("FullscreenUI", "Game path copied to clipboard.");
TRANSLATE_NOOP("FullscreenUI", "Automatic");
TRANSLATE_NOOP("FullscreenUI", "None");
TRANSLATE_NOOP("FullscreenUI", "Automatic");
TRANSLATE_NOOP("FullscreenUI", "Browse...");
TRANSLATE_NOOP("FullscreenUI", "Create New...");
TRANSLATE_NOOP("FullscreenUI", "Enter custom HDD size in gigabytes (402000):");
@@ -8966,6 +9282,11 @@ TRANSLATE_NOOP("FullscreenUI", "Appearance");
TRANSLATE_NOOP("FullscreenUI", "Selects the color style to be used for Big Picture Mode.");
TRANSLATE_NOOP("FullscreenUI", "When Big Picture mode is started, the game list will be displayed instead of the main menu.");
TRANSLATE_NOOP("FullscreenUI", "Show a save state selector UI when switching slots instead of showing a notification bubble.");
TRANSLATE_NOOP("FullscreenUI", "Background");
TRANSLATE_NOOP("FullscreenUI", "Select a custom background image to use in Big Picture Mode menus.");
TRANSLATE_NOOP("FullscreenUI", "Removes the custom background image.");
TRANSLATE_NOOP("FullscreenUI", "Sets the transparency of the custom background image.");
TRANSLATE_NOOP("FullscreenUI", "Select how to display the background image.");
TRANSLATE_NOOP("FullscreenUI", "Behaviour");
TRANSLATE_NOOP("FullscreenUI", "Prevents the screen saver from activating and the host from sleeping while emulation is running.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a game is started.");
@@ -9434,6 +9755,9 @@ TRANSLATE_NOOP("FullscreenUI", "Scarlet Devil");
TRANSLATE_NOOP("FullscreenUI", "Violet Angel");
TRANSLATE_NOOP("FullscreenUI", "Cobalt Sky");
TRANSLATE_NOOP("FullscreenUI", "AMOLED");
TRANSLATE_NOOP("FullscreenUI", "Fit");
TRANSLATE_NOOP("FullscreenUI", "Fill");
TRANSLATE_NOOP("FullscreenUI", "Stretch");
TRANSLATE_NOOP("FullscreenUI", "Enabled");
TRANSLATE_NOOP("FullscreenUI", "Disabled");
TRANSLATE_NOOP("FullscreenUI", "Top Left");
@@ -9649,6 +9973,11 @@ TRANSLATE_NOOP("FullscreenUI", "Clear Settings");
TRANSLATE_NOOP("FullscreenUI", "Theme");
TRANSLATE_NOOP("FullscreenUI", "Default To Game List");
TRANSLATE_NOOP("FullscreenUI", "Use Save State Selector");
TRANSLATE_NOOP("FullscreenUI", "Background Image");
TRANSLATE_NOOP("FullscreenUI", "Select Background Image");
TRANSLATE_NOOP("FullscreenUI", "Clear Background Image");
TRANSLATE_NOOP("FullscreenUI", "Background Opacity");
TRANSLATE_NOOP("FullscreenUI", "Background Mode");
TRANSLATE_NOOP("FullscreenUI", "Inhibit Screensaver");
TRANSLATE_NOOP("FullscreenUI", "Pause On Start");
TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss");
@@ -9824,6 +10153,10 @@ TRANSLATE_NOOP("FullscreenUI", "Compression Method");
TRANSLATE_NOOP("FullscreenUI", "Compression Level");
TRANSLATE_NOOP("FullscreenUI", "Use Debug Device");
TRANSLATE_NOOP("FullscreenUI", "Resume Game");
TRANSLATE_NOOP("FullscreenUI", "Close Game");
TRANSLATE_NOOP("FullscreenUI", "Back To Pause Menu");
TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving");
TRANSLATE_NOOP("FullscreenUI", "Leaderboards");
TRANSLATE_NOOP("FullscreenUI", "Toggle Frame Limit");
TRANSLATE_NOOP("FullscreenUI", "Game Properties");
TRANSLATE_NOOP("FullscreenUI", "Achievements");
@@ -9831,11 +10164,7 @@ TRANSLATE_NOOP("FullscreenUI", "Save Screenshot");
TRANSLATE_NOOP("FullscreenUI", "Switch To Software Renderer");
TRANSLATE_NOOP("FullscreenUI", "Switch To Hardware Renderer");
TRANSLATE_NOOP("FullscreenUI", "Change Disc");
TRANSLATE_NOOP("FullscreenUI", "Close Game");
TRANSLATE_NOOP("FullscreenUI", "Exit Without Saving");
TRANSLATE_NOOP("FullscreenUI", "Back To Pause Menu");
TRANSLATE_NOOP("FullscreenUI", "Exit And Save State");
TRANSLATE_NOOP("FullscreenUI", "Leaderboards");
TRANSLATE_NOOP("FullscreenUI", "Delete Save");
TRANSLATE_NOOP("FullscreenUI", "Close Menu");
TRANSLATE_NOOP("FullscreenUI", "Default Boot");

View File

@@ -622,7 +622,7 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
else
APPEND("IR={} ", static_cast<unsigned>(GSConfig.UpscaleMultiplier));
APPEND("B={} PL={} ", static_cast<unsigned>(GSConfig.AccurateBlendingUnit), static_cast<unsigned>(GSConfig.TexturePreloading));
APPEND("BL={} TPL={} ", static_cast<unsigned>(GSConfig.AccurateBlendingUnit), static_cast<unsigned>(GSConfig.TexturePreloading));
if (GSConfig.GPUPaletteConversion)
APPEND("PLTX ");
@@ -964,9 +964,20 @@ namespace SaveStateSelectorUI
static void RefreshHotkeyLegend();
static void Draw();
static void ShowSlotOSDMessage();
static std::string GetSaveStateTimestampSummary(const std::time_t& modification_time);
bool IsOpen();
static constexpr const char* DATE_TIME_FORMAT = TRANSLATE_NOOP("ImGuiOverlays", "Saved at {0:%H:%M} on {0:%a} {0:%Y/%m/%d}.");
static constexpr const char* SAVED_AGO_DAYS_TIME_DATE =
TRANSLATE_NOOP("ImGuiOverlays", "Saved {0} days ago at {1:%H:%M} on {1:%a} {1:%Y/%m/%d}");
static constexpr const char* SAVED_FUTURE_TIME_DATE =
TRANSLATE_NOOP("ImGuiOverlays", "Saved in the future at {0:%H:%M} on {0:%a} {0:%Y/%m/%d}");
static constexpr const char* SAVED_AGO_HOURS_MINUTES =
TRANSLATE_NOOP("ImGuiOverlays", "Saved {0} hours, {1} minutes ago at {2:%H:%M}");
static constexpr const char* SAVED_AGO_MINUTES = TRANSLATE_NOOP("ImGuiOverlays", "Saved {0} minutes ago at {1:%H:%M}");
static constexpr const char* SAVED_AGO_SECONDS = TRANSLATE_NOOP("ImGuiOverlays", "Saved {} seconds ago");
static constexpr const char* SAVED_AGO_NOW = TRANSLATE_NOOP("ImGuiOverlays", "Saved just now");
static constexpr std::time_t ONE_HOUR = 60 * 60; // 3600
static constexpr std::time_t TWENTY_FOUR_HOURS = ONE_HOUR * 24; // 86400
static std::shared_ptr<GSTexture> s_placeholder_texture;
static std::string s_load_legend;
@@ -1144,14 +1155,7 @@ void SaveStateSelectorUI::InitializeListEntry(const std::string& serial, u32 crc
}
li->title = fmt::format(TRANSLATE_FS("ImGuiOverlays", "Save Slot {0}"), slot);
std::tm tm_local = {};
#ifdef _MSC_VER
localtime_s(&tm_local, &sd.ModificationTime);
#else
localtime_r(&sd.ModificationTime, &tm_local);
#endif
li->summary = fmt::format(TRANSLATE_FS("ImGuiOverlays", DATE_TIME_FORMAT), tm_local);
li->summary = GetSaveStateTimestampSummary(sd.ModificationTime);
li->filename = Path::GetFileName(path);
u32 screenshot_width, screenshot_height;
@@ -1173,7 +1177,7 @@ void SaveStateSelectorUI::InitializeListEntry(const std::string& serial, u32 crc
void SaveStateSelectorUI::InitializePlaceholderListEntry(ListEntry* li, std::string path, s32 slot)
{
li->title = fmt::format(TRANSLATE_FS("ImGuiOverlays", "Save Slot {0}"), slot);
li->summary = TRANSLATE_STR("ImGuiOverlays", "No save present in this slot.");
li->summary = TRANSLATE_STR("ImGuiOverlays", "No save present in this slot");
li->filename = Path::GetFileName(path);
}
@@ -1349,26 +1353,15 @@ void SaveStateSelectorUI::ShowSlotOSDMessage()
const std::string serial = VMManager::GetDiscSerial();
const std::string filename = VMManager::GetSaveStateFileName(serial.c_str(), crc, slot);
FILESYSTEM_STAT_DATA sd;
std::string date;
std::tm tm_local = {};
std::string timestamp_summary;
if (!filename.empty() && FileSystem::StatFile(filename.c_str(), &sd))
{
#ifdef _MSC_VER
localtime_s(&tm_local, &sd.ModificationTime);
#else
localtime_r(&sd.ModificationTime, &tm_local);
#endif
date = fmt::format(TRANSLATE_FS("ImGuiOverlays", DATE_TIME_FORMAT), tm_local);
}
timestamp_summary = GetSaveStateTimestampSummary(sd.ModificationTime);
else
{
date = TRANSLATE_STR("ImGuiOverlays", "no save yet");
}
timestamp_summary = TRANSLATE_STR("ImGuiOverlays", "no save yet");
Host::AddIconOSDMessage("ShowSlotOSDMessage", ICON_FA_MAGNIFYING_GLASS,
fmt::format(TRANSLATE_FS("Hotkeys", "Save slot {0} selected ({1})."), slot, date),
fmt::format(TRANSLATE_FS("Hotkeys", "Save slot {0} selected ({1})."), slot, timestamp_summary),
Host::OSD_QUICK_DURATION);
}
@@ -1388,3 +1381,47 @@ void ImGuiManager::RenderOverlays()
if (SaveStateSelectorUI::s_open)
SaveStateSelectorUI::Draw();
}
std::string SaveStateSelectorUI::GetSaveStateTimestampSummary(const std::time_t& modification_time)
{
std::tm tm_modification_local = {};
#ifdef _MSC_VER
localtime_s(&tm_modification_local, &modification_time);
#else
localtime_r(&modification_time, &tm_modification_local);
#endif
const std::time_t current_time = std::time(nullptr);
const std::time_t time_since_save = current_time - std::mktime(&tm_modification_local);
if (time_since_save >= TWENTY_FOUR_HOURS)
{
return fmt::format(TRANSLATE_FS("ImGuiOverlays", SAVED_AGO_DAYS_TIME_DATE),
time_since_save / TWENTY_FOUR_HOURS, tm_modification_local);
}
else if (time_since_save >= ONE_HOUR)
{
return fmt::format(TRANSLATE_FS("ImGuiOverlays", SAVED_AGO_HOURS_MINUTES),
time_since_save / ONE_HOUR, (time_since_save / 60) % 60, tm_modification_local);
}
else if (time_since_save >= 60)
{
return fmt::format(TRANSLATE_FS("ImGuiOverlays", SAVED_AGO_MINUTES),
time_since_save / 60, tm_modification_local);
}
else if (time_since_save >= 5)
{
return fmt::format(TRANSLATE_FS("ImGuiOverlays", SAVED_AGO_SECONDS),
time_since_save);
}
else if (time_since_save >= 0)
{
return TRANSLATE_STR("ImGuiOverlays", SAVED_AGO_NOW);
}
else
{
return fmt::format(TRANSLATE_FS("ImGuiOverlays", SAVED_FUTURE_TIME_DATE),
tm_modification_local);
}
}

View File

@@ -84,7 +84,7 @@ void intBreakpoint(bool memcheck)
void intMemcheck(u32 op, u32 bits, bool store)
{
// compute accessed address
u32 start = cpuRegs.GPR.r[(op >> 21) & 0x1F].UD[0];
u32 start = cpuRegs.GPR.r[(op >> 21) & 0x1F].UL[0];
if (static_cast<s16>(op) != 0)
start += static_cast<s16>(op);
if (bits == 128)

View File

@@ -44,39 +44,39 @@
typedef struct
{
unsigned int mode;
unsigned int attr;
unsigned int size;
unsigned char ctime[8];
unsigned char atime[8];
unsigned char mtime[8];
unsigned int hisize;
u32 mode;
u32 attr;
u32 size;
u8 ctime[8];
u8 atime[8];
u8 mtime[8];
u32 hisize;
} fio_stat_t;
typedef struct
{
fio_stat_t _fioStat;
/** Number of subs (main) / subpart number (sub) */
unsigned int private_0;
unsigned int private_1;
unsigned int private_2;
unsigned int private_3;
unsigned int private_4;
u32 private_0;
u32 private_1;
u32 private_2;
u32 private_3;
u32 private_4;
/** Sector start. */
unsigned int private_5;
u32 private_5;
} fxio_stat_t;
typedef struct
{
fio_stat_t stat;
char name[256];
unsigned int unknown;
u32 unknown;
} fio_dirent_t;
typedef struct
{
fxio_stat_t stat;
char name[256];
unsigned int unknown;
u32 unknown;
} fxio_dirent_t;
static std::string hostRoot;
@@ -171,7 +171,7 @@ namespace R3000A
if (!FileSystem::StatFile(file_path.c_str(), &file_stats))
return -IOP_ENOENT;
host_stats->size = file_stats.st_size;
host_stats->size = (u32)file_stats.st_size;
host_stats->hisize = 0;
// Convert the mode.
@@ -315,13 +315,13 @@ namespace R3000A
switch (whence)
{
case IOP_SEEK_SET:
err = ::lseek(fd, offset, SEEK_SET);
err = static_cast<int>(::lseek(fd, offset, SEEK_SET));
break;
case IOP_SEEK_CUR:
err = ::lseek(fd, offset, SEEK_CUR);
err = static_cast<int>(::lseek(fd, offset, SEEK_CUR));
break;
case IOP_SEEK_END:
err = ::lseek(fd, offset, SEEK_END);
err = static_cast<int>(::lseek(fd, offset, SEEK_END));
break;
default:
return -IOP_EIO;
@@ -332,12 +332,12 @@ namespace R3000A
virtual int read(void* buf, u32 count) /* Flawfinder: ignore */
{
return translate_error(::read(fd, buf, count));
return translate_error(static_cast<int>(::read(fd, buf, count)));
}
virtual int write(void* buf, u32 count)
{
return translate_error(::write(fd, buf, count));
return translate_error(static_cast<int>(::write(fd, buf, count)));
}
};
@@ -762,7 +762,7 @@ namespace R3000A
v0 = host_stat(full_path, (fxio_stat_t*)&buf);
for (size_t i = 0; i < sizeof(fxio_stat_t); i++)
iopMemWrite8(data + i, buf[i]);
iopMemWrite8(static_cast<u32>(data + i), buf[i]);
}
else
{
@@ -770,7 +770,7 @@ namespace R3000A
v0 = host_stat(full_path, (fio_stat_t*)&buf);
for (size_t i = 0; i < sizeof(fio_stat_t); i++)
iopMemWrite8(data + i, buf[i]);
iopMemWrite8(static_cast<u32>(data + i), buf[i]);
}
pc = ra;
return 1;

View File

@@ -186,7 +186,7 @@ void VU_Thread::ExecuteRingBuffer()
break;
case MTVU_VIF_UNPACK:
{
u32 vif_copy_size = (uptr)&vif.StructEnd - (uptr)&vif.tag;
u32 vif_copy_size = static_cast<u32>((uptr)&vif.StructEnd - (uptr)&vif.tag);
Read(&vif.tag, vif_copy_size);
ReadRegs(&vifRegs);
u32 size = Read();
@@ -467,7 +467,7 @@ void VU_Thread::ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop, u32 fbrst)
void VU_Thread::VifUnpack(vifStruct& _vif, VIFregisters& _vifRegs, const u8* data, u32 size)
{
MTVU_LOG("MTVU - VifUnpack!");
u32 vif_copy_size = (uptr)&_vif.StructEnd - (uptr)&_vif.tag;
u32 vif_copy_size = (u32)((uptr)&_vif.StructEnd - (uptr)&_vif.tag);
ReserveSpace(1 + size_u32(vif_copy_size) + size_u32(sizeof(VIFregistersMTVU)) + 1 + size_u32(size));
Write(MTVU_VIF_UNPACK);
Write(&_vif.tag, vif_copy_size);

View File

@@ -77,11 +77,11 @@ namespace Patch
u8* data_ptr;
// needed because of the pointer
PatchCommand() { std::memset(this, 0, sizeof(*this)); }
PatchCommand() { std::memset(static_cast<void*>(this), 0, sizeof(*this)); }
PatchCommand(const PatchCommand& p) = delete;
PatchCommand(PatchCommand&& p)
{
std::memcpy(this, &p, sizeof(*this));
std::memcpy(static_cast<void*>(this), &p, sizeof(*this));
p.data_ptr = nullptr;
}
~PatchCommand()
@@ -93,7 +93,7 @@ namespace Patch
PatchCommand& operator=(const PatchCommand& p) = delete;
PatchCommand& operator=(PatchCommand&& p)
{
std::memcpy(this, &p, sizeof(*this));
std::memcpy(static_cast<void*>(this), &p, sizeof(*this));
p.data_ptr = nullptr;
return *this;
}

View File

@@ -708,8 +708,8 @@ std::optional<bool> Pcsx2Config::GSOptions::TriStateToOptionalBoolean(int value)
Pcsx2Config::GSOptions::GSOptions()
{
bitset[0] = 0;
bitset[1] = 0;
bitsets[0] = 0;
bitsets[1] = 0;
PCRTCAntiBlur = true;
DisableInterlaceOffset = false;
@@ -798,7 +798,8 @@ bool Pcsx2Config::GSOptions::operator==(const GSOptions& right) const
bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
{
return (
OpEqu(bitset) &&
OpEqu(bitsets[0]) &&
OpEqu(bitsets[1]) &&
OpEqu(InterlaceMode) &&
OpEqu(LinearPresent) &&
@@ -1843,6 +1844,7 @@ bool Pcsx2Config::PadOptions::Port::operator!=(const PadOptions::Port& right) co
Pcsx2Config::AchievementsOptions::AchievementsOptions()
{
bitset = 0;
Enabled = false;
HardcoreMode = false;
EncoreMode = false;

View File

@@ -66,12 +66,12 @@ const std::string& InputRecordingFile::getFilename() const noexcept
return m_filename;
}
unsigned long InputRecordingFile::getTotalFrames() const noexcept
u32 InputRecordingFile::getTotalFrames() const noexcept
{
return m_totalFrames;
}
unsigned long InputRecordingFile::getUndoCount() const noexcept
u32 InputRecordingFile::getUndoCount() const noexcept
{
return m_undoCount;
}
@@ -237,7 +237,7 @@ std::vector<PadData> InputRecordingFile::bulkReadPadData(u32 frameStart, u32 fra
}
// TODO - no multi-tap support
for (uint64_t currFrame = frameStart; currFrame < frameEnd; currFrame++)
for (u32 currFrame = frameStart; currFrame < frameEnd; currFrame++)
{
const auto padData = readPadData(currFrame, port, 0);
if (padData)

View File

@@ -68,8 +68,8 @@ public:
// Retrieve the input recording's filename (not the path)
const std::string& getFilename() const noexcept;
unsigned long getTotalFrames() const noexcept;
unsigned long getUndoCount() const noexcept;
u32 getTotalFrames() const noexcept;
u32 getUndoCount() const noexcept;
void logRecordingMetadata();
std::vector<PadData> bulkReadPadData(u32 frameStart, u32 frameEnd, const uint port);
@@ -92,8 +92,8 @@ private:
bool m_savestate = false;
// An signed 32-bit frame limit is equivalent to 1.13 years of continuous 60fps footage
unsigned long m_totalFrames = 0;
unsigned long m_undoCount = 0;
u32 m_totalFrames = 0;
u32 m_undoCount = 0;
// Calculates the position of the current frame in the input recording
size_t getRecordingBlockSeekPoint(const u32 frame) const noexcept;

View File

@@ -56,11 +56,10 @@ namespace usb_eyetoy
static void store_mpeg_frame(const unsigned char* data, const unsigned int len)
{
mpeg_mutex.lock();
std::lock_guard lock(mpeg_mutex);
if (len > 0)
memcpy(mpeg_buffer.start, data, len);
mpeg_buffer.length = len;
mpeg_mutex.unlock();
}
static void process_image(const unsigned char* data, int size)
@@ -642,13 +641,12 @@ namespace usb_eyetoy
int V4L2::GetImage(uint8_t* buf, size_t len)
{
mpeg_mutex.lock();
std::lock_guard lock(mpeg_mutex);
int len2 = mpeg_buffer.length;
if (len < mpeg_buffer.length)
len2 = len;
memcpy(buf, mpeg_buffer.start, len2);
mpeg_buffer.length = 0;
mpeg_mutex.unlock();
return len2;
};

View File

@@ -374,11 +374,10 @@ namespace usb_eyetoy
void store_mpeg_frame(const unsigned char* data, const unsigned int len)
{
mpeg_mutex.lock();
std::lock_guard lock(mpeg_mutex);
if (len > 0)
memcpy(mpeg_buffer.start, data, len);
mpeg_buffer.length = len;
mpeg_mutex.unlock();
}
void dshow_callback(unsigned char* data, int len, int bitsperpixel)
@@ -593,13 +592,12 @@ namespace usb_eyetoy
int DirectShow::GetImage(uint8_t* buf, size_t len)
{
mpeg_mutex.lock();
std::lock_guard lock(mpeg_mutex);
int len2 = mpeg_buffer.length;
if (static_cast<size_t>(len) < mpeg_buffer.length)
len2 = len;
memcpy(buf, mpeg_buffer.start, len2);
mpeg_buffer.length = 0;
mpeg_mutex.unlock();
return len2;
};

View File

@@ -177,7 +177,7 @@ static bool LoadBiosVersion(std::FILE* fp, u32& version, std::string& descriptio
"",
serial.c_str());
version = strtol(vermaj, (char**)NULL, 0) << 8;
version = static_cast<u32>(strtol(vermaj, (char**)NULL, 0) << 8);
version |= strtol(vermin, (char**)NULL, 0);
Console.WriteLn("BIOS Found: %s", description.c_str());

View File

@@ -174,7 +174,7 @@ namespace vtlb_private
/// Assumes the entry is a handler, and gets the raw handler ID
u8 assumeHandlerGetID() const { return value; }
/// Assumes the entry is a handler, and gets the physical address
u32 assumeHandlerGetPAddr(u32 vaddr) const { return (value + vaddr - assumeHandlerGetID()) & ~POINTER_SIGN_BIT; }
u32 assumeHandlerGetPAddr(u32 vaddr) const { return static_cast<u32>((value + vaddr - assumeHandlerGetID()) & ~POINTER_SIGN_BIT); }
/// Assumes the entry is a handler, returning it as a void*
void *assumeHandlerGetRaw(int index, bool write) const;
/// Assumes the entry is a handler, returning it