Compare commits

..

44 Commits

Author SHA1 Message Date
KamFretoZ 42ce97aca5 Qt: Clean up Setup Wizard's labels 2026-07-11 15:41:27 +02:00
KamFretoZ a4df5c300e Qt/Launcher: Fix StartupWMClass Value 2026-07-11 15:41:27 +02:00
KamFretoZ 4bf51e735a Qt: Add Create PCSX2 shortcut option to setup wizard 2026-07-11 15:41:27 +02:00
KamFretoZ d7e7c8164c Qt: Decouple CreateShortcut from the ShortcutCreationDialog 2026-07-11 15:41:27 +02:00
TJnotJT eb5e3fcd13 GS/HW: Make all vertices the same color in certain flat shading cases.
Fixes issues with AA1 with flat shading.
2026-07-10 10:16:51 +02:00
TheLastRar 8f8968cbb1 Deps: Revert workaround for MSVC ICE with PlutoVG
This reverts commit 7ade24fc77.
2026-07-09 18:40:47 -04:00
Silent ac83e93426 GameDB: Update Gran Turismo entries to use anchors and overrides 2026-07-09 15:53:38 -04:00
Silent 78d0cf4a4f YAML: Add support for resolving anchors, and use it for GameDB 2026-07-09 15:53:38 -04:00
lightningterror 2444c6090b GS: Fix Compiler warnings. 2026-07-09 18:06:50 +02:00
JordanTheToaster 7e49bc5a50 3rdparty: Update Vulkan-Headers to v1.4.356 2026-07-09 12:05:39 +02:00
JordanTheToaster 457719d0cc Deps: Update libjpegturbo to 3.2.0 2026-07-09 12:05:39 +02:00
JordanTheToaster c3e63d5a0e 3rdparty: Update CPUInfo to commit b1a5d63 2026-07-09 12:05:39 +02:00
TJnotJT 8c1b3f8ce3 QT: Remove ROV tabstop in graphics advanced section.
No longer used in this part of the GUI.
2026-07-09 11:44:18 +02:00
TJnotJT 55b37b794c GS:DX12: Index pipelines in correct shader cache table. 2026-07-09 11:43:44 +02:00
TJnotJT 1aca396e34 GS/DX12: Make sure null texture has correct descriptor type for slot. 2026-07-09 11:43:17 +02:00
PCSX2 Bot cb000a5101 [ci skip] Qt: Update Base Translation. 2026-07-08 20:48:48 -04:00
chaoticgd 1ae2a96747 Debugger: Allow strings to be edited from the symbol trees 2026-07-08 15:02:23 +02:00
SternXD 6834086c37 Achievements: Improve subset sidebar nav/focus behavior 2026-07-08 15:00:49 +02:00
SternXD b33b493c6e FullscreenUI: Use locale time on landing page instead of 24 hour 2026-07-08 15:00:49 +02:00
SternXD 437fe1e442 FullscreenUI: Move game info and cover art to top left of pause menu 2026-07-08 15:00:49 +02:00
TellowKrinkle 2a348dcff9 GS:MTL: Add ROV support for RW Textures Tier 1 hardware 2026-07-08 14:08:29 +02:00
TellowKrinkle 2821986fb2 GS:MTL: Implement ROV 2026-07-08 14:08:29 +02:00
TellowKrinkle 1a13f9707b GS:MTL: GPU ROV feature detection 2026-07-08 14:08:29 +02:00
TellowKrinkle ff1b47942c GS:MTL: Reduce unneccessary texture rebinds 2026-07-08 14:08:29 +02:00
TellowKrinkle 13e4973b48 GS:MTL: Fix texture flags 2026-07-08 14:08:29 +02:00
raid273 fd46054fb7 GameDB: Remove cpuSpriteRender from Mana-Khemia 2026-07-04 21:51:16 +02:00
raid273 f618f39bee GameDB: Various fixes 2026-07-04 21:51:16 +02:00
JordanTheToaster 603486ad2f Deps: Update SDL3 to v3.4.12 2026-07-04 21:49:31 +02:00
lightningterror a4b11651a4 GS: Better handle imgui texture creation hazards. 2026-07-04 20:40:55 +02:00
PCSX2 Bot b6e9a04433 [ci skip] Qt: Update Base Translation. 2026-07-03 20:22:06 -04:00
Mrlinkwii 05a2bbbf35 GameDB: remove gamefixes from silent hill 3 2026-07-03 16:09:21 -04:00
Oleg Tolmatcev 16b6572826 Misc: another COM code cleanup 2026-07-03 16:08:58 -04:00
Mrlinkwii 806f06fba6 Achievements: Rename softcore to Casual 2026-07-03 16:07:16 -04:00
TJnotJT 7a90caedec GS/HW: Force depth ROV if alpha test needs depth feedback. 2026-07-02 19:46:47 +02:00
TJnotJT 017b4d07da GS/HW: Improve ROV discard behavior.
Try to avoid color/depth write when it's discarded.
2026-07-02 19:46:47 +02:00
lightningterror 05015bc476 GS/HW: Better handle some texture creation hazards.
BeginDSAsRT and ConvertTextureTypeROVSingle make sure the textures are actually created.
2026-07-02 11:24:08 +02:00
lightningterror 1f0f3b92b3 GS/DX12: Expand the fb optimization hit detection. 2026-07-02 11:21:49 +02:00
JordanTheToaster fe3ce7f578 GameDB: Various Gran Turismo fixes 2026-07-01 11:40:37 +02:00
PCSX2 Bot 51c995168a [ci skip] PAD: Update to latest controller database. 2026-06-29 18:57:33 +02:00
PCSX2 Bot f83e4ff4bf [ci skip] Qt: Update Base Translation. 2026-06-28 20:26:52 -04:00
chaoticgd b67e793283 PINE: Disallow access to pages with custom VTLB handlers 2026-06-28 16:46:13 -04:00
chaoticgd d87ec54a5f VTLB: Rewrite vtlb_ramRead function template
Previously the declaration didn't match the definition, so you couldn't
actually call it.
2026-06-28 16:46:13 -04:00
TJnotJT a6b1040caf GS: Update target TEX0 on a move if buffer width is increased.
Co-authored-by: refractionpcsx2
2026-06-28 21:51:10 +02:00
TheLastRar 9650c4b79a GS/HW: Tweak AF shader 2026-06-28 21:46:04 +02:00
109 changed files with 4710 additions and 3319 deletions
@@ -23,11 +23,11 @@ QTAPNG=1.3.0
FFMPEG=8.1
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBPNG=1.6.58
LIBWEBP=1.6.0
NVENC=13.0.19.0
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
LZ4=1.10.0
VULKAN=1.4.328.1
ZSTD=1.5.7
@@ -57,10 +57,10 @@ f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
96e5c2d7f2c482a60d5804da48a2eb9a0db0719b2c65dcc169fbfdcf37f3a45d libbacktrace-$LIBBACKTRACE.tar.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
13da39edb3a40ed9713ae390ca89faa2f1202c9dda869ef306a8d4383e242bee nv-codec-headers-$NVENC.tar.gz
@@ -14,8 +14,8 @@
"sources": [
{
"type": "archive",
"url": "https://libsdl.org/release/SDL3-3.4.10.tar.gz",
"sha256": "12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785"
"url": "https://libsdl.org/release/SDL3-3.4.12.tar.gz",
"sha256": "f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7"
}
],
"cleanup": [
@@ -3,7 +3,7 @@ Version=1.0
Terminal=false
Type=Application
Name=PCSX2
StartupWMClass=PCSX2
StartupWMClass=pcsx2-qt
GenericName=PlayStation 2 Emulator
Comment=Sony PlayStation 2 emulator
Exec=pcsx2-qt
@@ -47,12 +47,12 @@ QT=6.11.1
QTAPNG=1.3.0
FREETYPE=2.14.3
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
HARFBUZZ=14.2.0
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.58
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBWEBP=1.6.0
FFMPEG=8.1
MOLTENVK=1.4.1
@@ -93,14 +93,14 @@ b2bf6c6845ac175ed7f819145483ba4676f617aaa6a5012c8efee63c8bbac413 qtimageformats
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
c652d5d94971031654ab3989891a490a895d3e3f2b71171c62692b28e94b1b93 harfbuzz-$HARFBUZZ.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c MoltenVK-$MOLTENVK.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
@@ -24,12 +24,12 @@ QT=6.11.1
QTAPNG=1.3.0
FREETYPE=2.14.3
SDL=SDL3-3.4.10
SDL=SDL3-3.4.12
HARFBUZZ=14.2.0
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.58
LIBJPEGTURBO=3.1.4.1
LIBJPEGTURBO=3.2.0
LIBWEBP=1.6.0
FFMPEG=8.1
MOLTENVK=1.4.1
@@ -69,14 +69,14 @@ b2bf6c6845ac175ed7f819145483ba4676f617aaa6a5012c8efee63c8bbac413 qtimageformats
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
12b34280415ec8418c864408b93d008a20a6530687ee613d60bfbd20411f2785 $SDL.tar.gz
f07b958a9ac5020fb7a44cadb957f658b2149c3c8abb4f63145fac9303249db7 $SDL.tar.gz
c652d5d94971031654ab3989891a490a895d3e3f2b71171c62692b28e94b1b93 harfbuzz-$HARFBUZZ.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
28eb403f51f0f7405249132cecfe82ea5c0ef97f1b32c5a65828814ae0d34775 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 libpng-$LIBPNG-apng.patch.gz
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG.tar.xz
9985f141902a17de818e264d17c1ce334b748e499ee02fcb4703e4dc0038f89c v$MOLTENVK.tar.gz
51dbf24fe72e43dd7cb9a289d3cab47112010f1a2ed69b6fc8ac0dff31991ed2 KDDockWidgets-$KDDOCKWIDGETS.tar.gz
+4 -4
View File
@@ -60,8 +60,8 @@ set QTAPNG=1.3.0
set FREETYPE=2.14.3
set HARFBUZZ=14.2.0
set SDL=SDL3-3.4.10
set LIBJPEGTURBO=3.1.4.1
set SDL=SDL3-3.4.12
set LIBJPEGTURBO=3.2.0
set LIBPNG=1658
set LIBPNGLONG=1.6.58
set LZ4=1.10.0
@@ -92,9 +92,9 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip bb2f83255706b1c92d731541c7cefaf98bb5b93e8f76d16f6deda05225ff20ee || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1658.zip b32f170855dbbe3e6d9e645af40b538137041773672c3ba3e02db5816c82d376 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e || 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" be8dafef3ecc956e736cfd1c242c10737f3ad589a866f25ba437b7f2f6d5ced2 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a49c0451e775a0c1e9022092c19fdef41ba38a83fcf031c5a6496e2 || 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://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
+4 -5
View File
@@ -77,8 +77,8 @@ set LIBX264=b35605ace3ddf7c1a5d67a2eb553f034aef41d55
set FREETYPE=2.14.3
set HARFBUZZ=14.2.0
set SDL=SDL3-3.4.10
set LIBJPEGTURBO=3.1.4.1
set SDL=SDL3-3.4.12
set LIBJPEGTURBO=3.2.0
set LIBPNG=1658
set LIBPNGLONG=1.6.58
set LZ4=1.10.0
@@ -120,9 +120,9 @@ call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip bb2f83255706b1c92d731541c7cefaf98bb5b93e8f76d16f6deda05225ff20ee || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1658.zip b32f170855dbbe3e6d9e645af40b538137041773672c3ba3e02db5816c82d376 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz eee7dea22ed502868017971c86c63c4ed1e6085de0baebfdcc3d3322f00f3eb0 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 6f30092cef9fb839779646608f4ee14ae3cbac989c47fa05e841b0841f09878e || 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" be8dafef3ecc956e736cfd1c242c10737f3ad589a866f25ba437b7f2f6d5ced2 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 3d4de8967a49c0451e775a0c1e9022092c19fdef41ba38a83fcf031c5a6496e2 || 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://github.com/madler/zlib/releases/download/v%ZLIB%/zlib%ZLIBSHORT%.zip" e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28 || goto error
call :downloadfile "zstd-%ZSTD%.zip" "https://github.com/facebook/zstd/archive/refs/tags/v%ZSTD%.zip" 7897bc5d620580d9b7cd3539c44b59d78f3657d33663fe97a145e07b4ebd69a4 || goto error
@@ -471,7 +471,6 @@ echo "Building PlutoVG..."
rmdir /S /Q "plutovg-%PLUTOVG%"
%SEVENZIP% x "plutovg-%PLUTOVG%.zip" || goto error
cd "plutovg-%PLUTOVG%" || goto error
%PATCH% -p1 < "%SCRIPTDIR%\plutovg-workaround-msvc-ice.patch" || goto error
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="%INSTALLDIR%" -DCMAKE_INSTALL_PREFIX="%INSTALLDIR%" -DBUILD_SHARED_LIBS=ON -DPLUTOVG_BUILD_EXAMPLES=OFF -B build -G Ninja || goto error
cmake --build build --parallel || goto error
ninja -C build install || goto error
@@ -1,52 +0,0 @@
diff --git a/source/plutovg-font.c b/source/plutovg-font.c
index 0ad89b1..110de7f 100644
--- a/source/plutovg-font.c
+++ b/source/plutovg-font.c
@@ -836,7 +836,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
size_t family_index = 0;
if(unicode_family_name) {
const stbtt_uint8* family_name = unicode_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i = family_length; i > 0; i -= 2) {
stbtt_uint16 ch = family_name[0] * 256 + family_name[1];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -853,7 +854,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
entry->family[family_index++] = (0x80 + ((c) & 0x3f));
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
+ i -= 2;
} else {
entry->family[family_index++] = (0xe0 + (ch >> 12));
entry->family[family_index++] = (0x80 + ((ch >> 6) & 0x3f));
@@ -861,7 +863,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 2;
- family_length -= 2;
+ //family_length -= 2;
}
entry->family[family_index] = '\0';
@@ -902,7 +904,8 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
};
const stbtt_uint8* family_name = roman_family_name;
- while(family_length) {
+ //while(family_length) {
+ for (int i2 = family_length; i2 > 0; i2--) {
stbtt_uint16 ch = MAC_ROMAN_TABLE[family_name[0]];
if(ch < 0x80) {
entry->family[family_index++] = ch;
@@ -916,7 +919,7 @@ int plutovg_font_face_cache_load_file(plutovg_font_face_cache_t* cache, const ch
}
family_name += 1;
- family_length -= 1;
+ //family_length -= 1;
}
entry->family[family_index] = '\0';
+2
View File
@@ -361,6 +361,8 @@ enum cpuinfo_uarch {
cpuinfo_uarch_raptor_cove = 0x0010020F,
/** Intel Redwood Cove microarchitecture (Granite Rapids). */
cpuinfo_uarch_redwood_cove = 0x00100210,
/** Intel Coyote Cove microarchitecture. */
cpuinfo_uarch_coyote_cove = 0x00100211,
/** Pentium 4 with Willamette, Northwood, or Foster cores. */
cpuinfo_uarch_willamette = 0x00100300,
+3
View File
@@ -1,3 +1,6 @@
/* for syscall() */
#define _DEFAULT_SOURCE
#include <stdbool.h>
#include <stddef.h>
+2
View File
@@ -36,6 +36,7 @@ enum cpuinfo_arm_chipset_vendor {
cpuinfo_arm_chipset_vendor_texas_instruments,
cpuinfo_arm_chipset_vendor_unisoc,
cpuinfo_arm_chipset_vendor_wondermedia,
cpuinfo_arm_chipset_vendor_google,
cpuinfo_arm_chipset_vendor_max,
};
@@ -73,6 +74,7 @@ enum cpuinfo_arm_chipset_series {
cpuinfo_arm_chipset_series_unisoc_t,
cpuinfo_arm_chipset_series_unisoc_ums,
cpuinfo_arm_chipset_series_wondermedia_wm,
cpuinfo_arm_chipset_series_google_tensor,
cpuinfo_arm_chipset_series_max,
};
+1 -1
View File
@@ -366,7 +366,7 @@ CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_f
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_hardware_chipname(
const char ro_hardware_chipname[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
const char ro_soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
const char soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]);
#else
CPUINFO_INTERNAL struct cpuinfo_arm_chipset cpuinfo_arm_linux_decode_chipset_from_proc_cpuinfo_revision(
const char proc_cpuinfo_revision[restrict static CPUINFO_REVISION_VALUE_MAX]);
+61 -10
View File
@@ -3492,9 +3492,20 @@ struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_chipname(
};
}
/*
* Decodes chipset name from ro.soc.model Android system property.
*
* @param[in] soc_model - ro.soc.model value.
*
* @returns Decoded chipset name. If chipset could not be decoded, the resulting
* structure would use `unknown` vendor and series identifiers.
*/
struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
const char soc_model[restrict static CPUINFO_BUILD_PROP_VALUE_MAX]) {
struct cpuinfo_arm_chipset chipset;
struct cpuinfo_arm_chipset chipset = {
.vendor = cpuinfo_arm_chipset_vendor_unknown,
.series = cpuinfo_arm_chipset_series_unknown,
};
const size_t soc_model_length = strnlen(soc_model, CPUINFO_BUILD_PROP_VALUE_MAX);
const char* soc_model_end = soc_model + soc_model_length;
@@ -3516,10 +3527,28 @@ struct cpuinfo_arm_chipset cpuinfo_arm_android_decode_chipset_from_ro_soc_model(
return chipset;
}
return (struct cpuinfo_arm_chipset){
.vendor = cpuinfo_arm_chipset_vendor_unknown,
.series = cpuinfo_arm_chipset_series_unknown,
};
if (soc_model[0] != '\0') {
if (strncmp(soc_model, "Tensor", 6) == 0) {
chipset.vendor = cpuinfo_arm_chipset_vendor_google;
chipset.series = cpuinfo_arm_chipset_series_google_tensor;
const char* suffix_start = soc_model + 6;
while (*suffix_start == ' ') {
suffix_start++;
}
const size_t suffix_length = strnlen(suffix_start, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
if (suffix_length > 0) {
strncpy(chipset.suffix, suffix_start, suffix_length);
}
return chipset;
} else if (strncmp(soc_model, "GS201", 5) == 0) {
chipset.vendor = cpuinfo_arm_chipset_vendor_google;
chipset.series = cpuinfo_arm_chipset_series_google_tensor;
strncpy(chipset.suffix, "G2", 2);
return chipset;
}
}
return chipset;
}
#endif /* __ANDROID__ */
@@ -3858,6 +3887,7 @@ static const char* chipset_vendor_string[cpuinfo_arm_chipset_vendor_max] = {
[cpuinfo_arm_chipset_vendor_texas_instruments] = "Texas Instruments",
[cpuinfo_arm_chipset_vendor_unisoc] = "Unisoc",
[cpuinfo_arm_chipset_vendor_wondermedia] = "WonderMedia",
[cpuinfo_arm_chipset_vendor_google] = "Google",
};
/* Map from ARM chipset series ID to its string representation */
@@ -3895,6 +3925,7 @@ static const char* chipset_series_string[cpuinfo_arm_chipset_series_max] = {
[cpuinfo_arm_chipset_series_unisoc_t] = "T",
[cpuinfo_arm_chipset_series_unisoc_ums] = "UMS",
[cpuinfo_arm_chipset_series_wondermedia_wm] = "WM",
[cpuinfo_arm_chipset_series_google_tensor] = "Tensor",
};
/* Convert chipset name represented by cpuinfo_arm_chipset structure to a string
@@ -3913,14 +3944,35 @@ void cpuinfo_arm_chipset_to_string(
const char* vendor_string = chipset_vendor_string[vendor];
const char* series_string = chipset_series_string[series];
const uint32_t model = chipset->model;
const size_t suffix_length = strnlen(chipset->suffix, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
if (model == 0) {
if (series == cpuinfo_arm_chipset_series_unknown) {
strncpy(name, vendor_string, CPUINFO_ARM_CHIPSET_NAME_MAX);
if (suffix_length > 0) {
if (series == cpuinfo_arm_chipset_series_unknown) {
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
"%s %.*s",
vendor_string,
(int)suffix_length,
chipset->suffix);
} else {
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
"%s %s %.*s",
vendor_string,
series_string,
(int)suffix_length,
chipset->suffix);
}
} else {
snprintf(name, CPUINFO_ARM_CHIPSET_NAME_MAX, "%s %s", vendor_string, series_string);
if (series == cpuinfo_arm_chipset_series_unknown) {
strncpy(name, vendor_string, CPUINFO_ARM_CHIPSET_NAME_MAX);
} else {
snprintf(name, CPUINFO_ARM_CHIPSET_NAME_MAX, "%s %s", vendor_string, series_string);
}
}
} else {
const size_t suffix_length = strnlen(chipset->suffix, CPUINFO_ARM_CHIPSET_SUFFIX_MAX);
snprintf(
name,
CPUINFO_ARM_CHIPSET_NAME_MAX,
@@ -4051,7 +4103,6 @@ static enum cpuinfo_arm_chipset_vendor disambiguate_chipset_vendor(
(vendor_a == cpuinfo_arm_chipset_vendor_spreadtrum && vendor_b == cpuinfo_arm_chipset_vendor_unisoc)) {
return cpuinfo_arm_chipset_vendor_unisoc;
}
return cpuinfo_arm_chipset_vendor_unknown;
}
+3 -3
View File
@@ -256,12 +256,12 @@ void cpuinfo_arm_linux_init(void) {
}
#if defined(__ANDROID__)
struct cpuinfo_android_properties android_properties;
struct cpuinfo_android_properties android_properties = {0};
cpuinfo_arm_android_parse_properties(&android_properties);
#else
char proc_cpuinfo_hardware[CPUINFO_HARDWARE_VALUE_MAX];
char proc_cpuinfo_hardware[CPUINFO_HARDWARE_VALUE_MAX] = {0};
#endif
char proc_cpuinfo_revision[CPUINFO_REVISION_VALUE_MAX];
char proc_cpuinfo_revision[CPUINFO_REVISION_VALUE_MAX] = {0};
if (!cpuinfo_arm_linux_parse_proc_cpuinfo(
#if defined(__ANDROID__)
+49
View File
@@ -11,6 +11,11 @@ void cpuinfo_arm_decode_vendor_uarch(
#endif /* CPUINFO_ARCH_ARM */
enum cpuinfo_vendor vendor[RESTRICT_STATIC 1],
enum cpuinfo_uarch uarch[RESTRICT_STATIC 1]) {
/* Ensure the out-parameters are always initialized, including for
* implementers that are not handled in the switch below. */
*vendor = cpuinfo_vendor_unknown;
*uarch = cpuinfo_uarch_unknown;
switch (midr_get_implementer(midr)) {
case 'A':
*vendor = cpuinfo_vendor_arm;
@@ -438,6 +443,50 @@ void cpuinfo_arm_decode_vendor_uarch(
midr_get_part(midr));
}
break;
case 'a':
*vendor = cpuinfo_vendor_apple;
switch (midr_get_part(midr)) {
case 0x022:
case 0x024:
case 0x028:
*uarch = cpuinfo_uarch_icestorm;
break;
case 0x023:
case 0x025:
case 0x029:
*uarch = cpuinfo_uarch_firestorm;
break;
case 0x032:
case 0x034:
case 0x038:
*uarch = cpuinfo_uarch_blizzard;
break;
case 0x033:
case 0x035:
case 0x039:
*uarch = cpuinfo_uarch_avalanche;
break;
case 0x042:
case 0x044:
case 0x048:
*uarch = cpuinfo_uarch_coll_sawtooth;
break;
case 0x043:
case 0x045:
case 0x049:
*uarch = cpuinfo_uarch_coll_everest;
break;
case 0x052:
*uarch = cpuinfo_uarch_donan_sawtooth;
break;
case 0x053:
*uarch = cpuinfo_uarch_donan_everest;
break;
default:
cpuinfo_log_warning(
"unknown Apple CPU part 0x%03" PRIx32 " ignored", midr_get_part(midr));
}
break;
#if CPUINFO_ARCH_ARM
case 'V':
*vendor = cpuinfo_vendor_marvell;
+9
View File
@@ -307,6 +307,15 @@ static bool transform_token(char* token_start, char* token_end, struct parser_st
}
break;
case 4:
/*
* Erase everything starting with "with" on AMD
* processors, e.g. "AMD Ryzen 5 PRO 6650U with Radeon
* Graphics"
*/
if (erase_matching(token_start, token_length, "with")) {
return false;
}
/* Remember to erase "Dual Core" in "AMD Athlon(tm) 64
* X2 Dual Core Processor 3800+" */
if (memcmp(token_start, "Dual", token_length) == 0) {
+7
View File
@@ -270,8 +270,15 @@ enum cpuinfo_uarch cpuinfo_x86_decode_uarch(
return cpuinfo_uarch_prescott;
}
break;
case 0x12:
switch (model_info->model) {
case 0x01: // Nova Lake P-core (Coyote Cove)
return cpuinfo_uarch_coyote_cove;
}
break;
}
break;
case cpuinfo_vendor_amd:
switch (model_info->family) {
#if CPUINFO_ARCH_X86
-2
View File
@@ -265,8 +265,6 @@ BOOL CALLBACK cpuinfo_x86_windows_init(PINIT_ONCE init_once, PVOID parameter, PV
* APIC order */
const uint32_t core_id = cores_count++;
uint32_t smt_id = 0;
/* Reconstruct core part of APIC ID */
const uint32_t core_apic_id = (core_id & core_bits_mask) << x86_processor.topology.core_bits_offset;
/* Iterate processor groups and set the core & SMT parts of APIC
* ID */
for (uint32_t i = 0; i < core_info->Processor.GroupCount; i++) {
+4 -3
View File
@@ -1,12 +1,13 @@
Copyright 2015-2023 The Khronos Group Inc.
Copyright 2015-2026 The Khronos Group Inc.
Files in this repository fall under one of these licenses:
- `Apache-2.0`
- `MIT`
Note: With the exception of `parse_dependency.py` the files using `MIT` license
also fall under `Apache-2.0`. Example:
Note: With the exception of `parse_dependency.py`, which is based on an
external file, files using the `MIT` license also fall under `Apache-2.0`.
Example:
```
SPDX-License-Identifier: Apache-2.0 OR MIT
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -2
View File
@@ -2,8 +2,7 @@
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#pragma once
+1 -2
View File
@@ -2,8 +2,7 @@
* Copyright 2015-2023 The Khronos Group Inc.
* Copyright 2015-2023 Valve Corporation
* Copyright 2015-2023 LunarG, Inc.
*
* SPDX-License-Identifier: Apache-2.0
* SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#pragma once
+1 -2
View File
@@ -3,8 +3,7 @@
//
/*
** Copyright 2014-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
+1 -2
View File
@@ -3,8 +3,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
#include "vk_platform.h"
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
+1 -1
View File
@@ -4,7 +4,7 @@
/*
** Copyright 2015-2026 The Khronos Group Inc.
**
** SPDX-License-Identifier: Apache-2.0
** SPDX-License-Identifier: Apache-2.0 OR MIT
*/
/*
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1262,6 +1262,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001530000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001630000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000c82d00001730000011010000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
06000000c82d00000320000007010000,8BitDo Ultimate Wired Controller for Xbox,a:b0,b:b1,x:b2,y:b3,back:b6,guide:b8,start:b7,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
03000000c82d00000121000011010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000c82d00000121000000010000,8BitDo Xbox One SN30 Pro,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
05000000a00500003232000001000000,8BitDo Zero,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,
+88 -100
View File
@@ -192,7 +192,7 @@ struct PS_INPUT
#ifdef PIXEL_SHADER
struct PS_OUTPUT_REAL
struct PS_OUTPUT
{
#define NUM_RTS 0
@@ -231,20 +231,6 @@ struct PS_OUTPUT_REAL
#undef NUM_RTS
};
struct PS_OUTPUT
{
#if !PS_NO_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float c;
#else
float4 c0;
#if !PS_NO_COLOR1
float4 c1;
#endif
#endif
#endif
};
Texture2D<float4> Texture : register(t0);
Texture2D<float4> Palette : register(t1);
#if !PS_ROV_COLOR
@@ -361,23 +347,28 @@ bool2 nan_or_inf(float2 xy)
float4 sample_c_af(float2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = any(nan_or_inf(uv)) ? float2(0, 0) : uv;
uv = any(nan_or_inf(uv)) ? float2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
float2 sz;
Texture.GetDimensions(sz.x, sz.y);
float2 dX = ddx(uv) * sz;
float2 dY = ddy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(nan_or_inf(dX) | nan_or_inf(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -385,42 +376,49 @@ float4 sample_c_af(float2 uv, float uv_w)
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float f = (dX.x * dY.y - dY.x * dX.y);
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float sqrt_num_plus = sqrt(F * (t + p));
float sqrt_num_minus = sqrt(F * (t - p));
float inv_sqrt_denom_plus = rsqrt(t * (q + t));
float inv_sqrt_denom_minus = rsqrt(t * (q - t));
float signB = sign(B);
float2 new_dX = float2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrt_num_plus * inv_sqrt_denom_plus,
sqrt_num_minus * inv_sqrt_denom_plus * signB
);
float2 new_dY = float2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrt_num_minus * inv_sqrt_denom_minus * -signB,
sqrt_num_plus * inv_sqrt_denom_minus
);
d_inf_nan = any(nan_or_inf(new_dX) | nan_or_inf(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
float2 aniso_line;
if (length_major <= 1.0f)
{
// A zero length_major would result in NaN Lod and break sampling.
@@ -428,57 +426,44 @@ float4 sample_c_af(float2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = float2(0, 0);
aniso_line = float2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
float2 aniso_line_dir = float2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
float2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
float4 colour;
if (aniso_ratio == 1.0f)
colour = Texture.SampleLevel(TextureSampler, uv, lod);
else
{
float4 num = float4(0, 0, 0, 0);
for (int i = 0; i < aniso_ratio; i++)
{
float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
float2 segment = (2.0f * aniso_line) / aniso_ratio;
int aniso_ratio_i = (int)aniso_ratio;
for (int i = 0; i < aniso_ratio_i; i++)
{
float2 d = -aniso_line + (0.5f + i) * segment;
float2 uv_sample = uv + d;
float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod);
num += sample_colour;
@@ -1327,13 +1312,17 @@ void ps_blend(inout float4 Color, inout float4 As_rgba, float2 pos_xy)
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
#define DISCARD rov_discard = true
#define DISCARD { rov_discard_color = true; rov_discard_depth = true; }
#define DISCARD_COLOR rov_discard_color = true
#define DISCARD_DEPTH rov_discard_depth = true
#else
#define DISCARD discard
#define DISCARD discard
#define DISCARD_COLOR o_col0 = RtLoad(input.p.xy)
#define DISCARD_DEPTH input.p.z = DepthLoad(input.p.xy)
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
PS_OUTPUT_REAL ps_main(PS_INPUT input)
PS_OUTPUT ps_main(PS_INPUT input)
#else
void ps_main(PS_INPUT input)
#endif
@@ -1352,7 +1341,8 @@ void ps_main(PS_INPUT input)
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
bool rov_discard = false;
bool rov_discard_color = false;
bool rov_discard_depth = false;
#endif
// Use ROV discard macro for since we cannot do
@@ -1459,19 +1449,29 @@ if (bad)
discard;
#endif
PS_OUTPUT output;
// Output values
#if !PS_NO_COLOR
#if PS_DATE == 1 || PS_DATE == 2
float o_col0;
#else
float4 o_col0;
#if !PS_NO_COLOR1
float4 o_col1;
#endif
#endif
#endif
// Get first primitive that will write a failling alpha value
#if PS_DATE == 1
// DATM == 0
// Pixel with alpha equal to 1 will failed (128-255)
output.c = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
o_col0 = (C.a > 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
#elif PS_DATE == 2
// DATM == 1
// Pixel with alpha equal to 0 will failed (0-127)
output.c = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
o_col0 = (C.a < 127.5f) ? float(input.primid) : float(0x7FFFFFFF);
#else
// Not primid DATE setup
@@ -1548,26 +1548,26 @@ if (bad)
// Output color scaling
#if !PS_NO_COLOR
output.c0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f;
output.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f;
o_col0.a = PS_RTA_CORRECTION ? C.a / 128.0f : C.a / 255.0f;
o_col0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.0f) : C.rgb / 255.0f;
#if !PS_NO_COLOR1
output.c1 = alpha_blend;
o_col1 = alpha_blend;
#endif
#endif // !PS_NO_COLOR
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (!atst_pass)
input.p.z = DepthLoad(input.p.xy);
DISCARD_DEPTH;
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (!atst_pass)
output.c0 = RtLoad(input.p.xy);
DISCARD_COLOR;
#elif PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
if (!atst_pass)
{
output.c0.a = RtLoad(input.p.xy).a;
o_col0.a = RtLoad(input.p.xy).a; // discard alpha
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
input.p.z = DepthLoad(input.p.xy);
DISCARD_DEPTH;
#endif
}
#endif
@@ -1580,46 +1580,39 @@ if (bad)
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
if (!bool(input.interior))
input.p.z = DepthLoad(input.p.xy); // No depth update for triangle edges.
DISCARD_DEPTH; // No depth update for triangle edges.
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
// Output struct with the actual system values output semantics.
PS_OUTPUT_REAL output_real;
PS_OUTPUT output;
#endif
// Color write back
#if PS_RETURN_COLOR
#if PS_DATE == 1 || PS_DATE == 2
output_real.c = output.c;
#else
output_real.c0 = output.c0;
#if !PS_NO_COLOR1
output_real.c1 = output.c1;
#endif
output.c0 = o_col0;
#if !PS_NO_COLOR1
output.c1 = o_col1;
#endif
#elif PS_RETURN_COLOR_ROV
output.c0 = (rov_discard | (FbMask == 0xFFu)) ? RtLoad(input.p.xy) : output.c0;
RtWrite(input.p.xy, output.c0);
o_col0 = (FbMask == 0xFFu) ? RtLoad(input.p.xy) : o_col0; // channel masking
if (!rov_discard_color)
RtWrite(input.p.xy, o_col0);
#endif
// Depth write back
#if PS_RETURN_DEPTH
output_real.depth = input.p.z;
output.depth = input.p.z;
#if SW_DEPTH && PS_NO_COLOR1 && PS_DEPTH_FEEDBACK_SUPPORT == 2
// Output color clone for feedback.
output_real.depth_color = input.p.z;
output.depth_color = input.p.z;
#endif
#elif PS_RETURN_DEPTH_ROV
#if SW_DEPTH
input.p.z = rov_discard ? DepthLoad(input.p.xy) : input.p.z;
#endif
DepthWrite(input.p.xy, input.p.z);
if (!rov_discard_depth)
DepthWrite(input.p.xy, input.p.z);
#endif
#if (PS_RETURN_COLOR || PS_RETURN_DEPTH)
return output_real;
return output;
#endif
}
@@ -2021,11 +2014,6 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
vtx.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vtx.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (first vertex in DX)
vtx.c = i0 == 0 ? vtx.c : (i1 == 0 ? other.c : opposite.c);
#endif
}
return vtx;
+43 -45
View File
@@ -220,22 +220,27 @@ float manual_lod(float uv_w)
vec4 sample_c_af(vec2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0, 0) : uv;
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
vec2 sz = textureSize(TextureSampler, 0);
vec2 dX = dFdx(uv) * sz;
vec2 dY = dFdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -243,38 +248,46 @@ vec4 sample_c_af(vec2 uv, float uv_w)
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float f = (dX.x * dY.y - dY.x * dX.y);
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = inversesqrt(denom_plus);
float inv_sqrt_denom_minus = inversesqrt(denom_minus);
vec2 new_dX = vec2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
vec2 new_dY = vec2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
@@ -286,57 +299,42 @@ vec4 sample_c_af(vec2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = vec2(0, 0);
aniso_line = vec2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
vec2 aniso_line_dir = vec2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
vec2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
vec4 colour;
if (aniso_ratio == 1.0f)
colour = textureLod(TextureSampler, uv, lod);
else
{
vec4 num = vec4(0, 0, 0, 0);
vec4 num = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec2 segment = (2.0f * aniso_line) / aniso_ratio;
for (int i = 0; i < aniso_ratio; i++)
{
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
{
vec2 d = -aniso_line + (0.5f + i) * segment;
vec2 uv_sample = uv + d;
vec4 sample_colour = textureLod(TextureSampler, uv_sample, lod);
num += sample_colour;
@@ -455,11 +455,6 @@ void main()
VSout.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
VSout.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in GL)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
+61 -63
View File
@@ -450,11 +450,6 @@ void main()
vsOut.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
vsOut.interior = 0;
#if !VS_IIP
// Get the provoking vertex color (last vertex in VK)
vtx.c = i0 == 2 ? vtx.c : (i1 == 2 ? other.c : opposite.c);
#endif
}
#endif
@@ -728,22 +723,27 @@ float manual_lod(float uv_w)
vec4 sample_c_af(vec2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0, 0) : uv;
uv = (any(isnan(uv)) || any(isinf(uv))) ? vec2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
vec2 sz = textureSize(Texture, 0);
vec2 dX = dFdx(uv) * sz;
vec2 dY = dFdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -758,31 +758,40 @@ vec4 sample_c_af(vec2 uv, float uv_w)
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = inversesqrt(denom_plus);
float inv_sqrt_denom_minus = inversesqrt(denom_minus);
vec2 new_dX = vec2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
vec2 new_dY = vec2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX)) || any(isinf(new_dY)) || any(isnan(new_dX)) || any(isnan(new_dY));
if (!d_inf_nan)
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
@@ -794,57 +803,42 @@ vec4 sample_c_af(vec2 uv, float uv_w)
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = vec2(0, 0);
aniso_line = vec2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
vec2 aniso_line_dir = vec2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
vec2 aniso_line_dir = is_major_x ? dX : dY;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_ANISOTROPIC_FILTERING)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_ANISOTROPIC_FILTERING;
length_lod = length_major / PS_ANISOTROPIC_FILTERING;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, PS_ANISOTROPIC_FILTERING);
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
#if PS_AUTOMATIC_LOD == 1
float lod = log2(length_lod);
#elif PS_MANUAL_LOD == 1
float lod = manual_lod(uv_w);
#else
float lod = 0; // No Lod
float lod = 0.0f; // No Lod
#endif
vec4 colour;
if (aniso_ratio == 1.0f)
colour = textureLod(Texture, uv, lod);
else
{
vec4 num = vec4(0, 0, 0, 0);
vec4 num = vec4(0.0f, 0.0f, 0.0f, 0.0f);
vec2 segment = (2.0f * aniso_line) / aniso_ratio;
for (int i = 0; i < aniso_ratio; i++)
{
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
{
vec2 d = -aniso_line + (0.5f + i) * segment;
vec2 uv_sample = uv + d;
vec4 sample_colour = textureLod(Texture, uv_sample, lod);
num += sample_colour;
@@ -1721,9 +1715,13 @@ layout(early_fragment_tests) in;
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
#define DISCARD rov_discard = true
#define DISCARD { rov_discard_color = true; rov_discard_depth = true; }
#define DISCARD_COLOR rov_discard_color = true
#define DISCARD_DEPTH rov_discard_depth = true
#else
#define DISCARD discard
#define DISCARD discard
#define DISCARD_COLOR o_col0 = sample_from_rt()
#define DISCARD_DEPTH input_z = sample_from_depth()
#endif
void main()
@@ -1748,7 +1746,8 @@ void main()
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
bool rov_discard = gl_HelperInvocation;
bool rov_discard_color = gl_HelperInvocation;
bool rov_discard_depth = gl_HelperInvocation;
#endif
#if PS_ZTST == ZTST_GEQUAL
@@ -1941,16 +1940,16 @@ void main()
// Alpha test with feedback
#if PS_AFAIL == AFAIL_FB_ONLY
if (!atst_pass)
input_z = sample_from_depth();
DISCARD_DEPTH;
#elif PS_AFAIL == AFAIL_ZB_ONLY
if (!atst_pass)
o_col0 = sample_from_rt();
DISCARD_COLOR;
#elif (PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
if (!atst_pass)
{
o_col0.a = sample_from_rt().a;
o_col0.a = sample_from_rt().a; // discard alpha
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
input_z = sample_from_depth();
DISCARD_DEPTH;
#endif
}
#endif
@@ -1962,24 +1961,23 @@ void main()
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
if (!bool(vsIn.interior))
input_z = sample_from_depth(); // No depth update for triangle edges.
DISCARD_DEPTH; // No depth update for triangle edges.
#endif
// Writing back color (result already written to o_col0 for non-ROV)
#if PS_RETURN_COLOR_ROV
bvec4 discard_channels = bvec4(uvec4(rov_discard) | uvec4(equal(FbMask, uvec4(0xFFu))));
o_col0 = mix(o_col0, sample_from_rt(), discard_channels);
o_col0 = mix(o_col0, sample_from_rt(), equal(FbMask, uvec4(0xFFu))); // channel masking
imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0);
if (!rov_discard_color)
imageStore(RtImageRov, ivec2(gl_FragCoord.xy), o_col0);
#endif
// Writing back depth
#if PS_RETURN_DEPTH
gl_FragDepth = input_z;
#elif PS_RETURN_DEPTH_ROV
input_z = rov_discard ? sample_from_depth() : input_z;
imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f));
if (!rov_discard_depth)
imageStore(DepthImageRov, ivec2(gl_FragCoord.xy), vec4(input_z, 0, 0, 1.0f));
#endif
#if PS_ROV_COLOR || PS_ROV_DEPTH
+39
View File
@@ -34,6 +34,34 @@ Value MemoryInterface::Read(u32 address, bool* valid)
return Value(0);
}
std::optional<std::string> MemoryInterface::ReadString(u32 address, u32 max_size, ReadStringFlags flags)
{
std::string string;
for (u32 i = 0; i < max_size; i++)
{
bool valid;
char c = Read8(address + i, &valid);
if (!valid)
return std::nullopt;
if (c == '\0')
return string;
else if (!(flags & ALLOW_NON_PRINTABLE_CHARACTERS) && (c < ' ' || c > '~'))
return std::nullopt;
string += c;
}
if (flags & ALLOW_LONG_STRINGS)
{
string += '~';
return string;
}
return std::nullopt;
}
template <MemoryAccessType Value>
bool MemoryInterface::Write(u32 address, Value value)
{
@@ -62,6 +90,17 @@ bool MemoryInterface::Write(u32 address, Value value)
return false;
}
bool MemoryInterface::WriteString(u32 address, std::string_view string)
{
if (!WriteBytes(address, string.data(), static_cast<u32>(string.size())))
return false;
if (!Write8(address + static_cast<u32>(string.size()), '\0'))
return false;
return true;
}
bool MemoryInterface::IdempotentWrite8(u32 address, u8 value)
{
bool valid;
+16 -2
View File
@@ -5,6 +5,9 @@
#include "Pcsx2Types.h"
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
template <typename Value>
@@ -31,16 +34,27 @@ public:
template <MemoryAccessType Value>
Value Read(u32 address, bool* valid = nullptr);
enum ReadStringFlags
{
NO_FLAGS = 0,
ALLOW_LONG_STRINGS = 1 << 0,
ALLOW_NON_PRINTABLE_CHARACTERS = 1 << 1
};
std::optional<std::string> ReadString(u32 address, u32 max_size = 256, ReadStringFlags flags = NO_FLAGS);
virtual bool Write8(u32 address, u8 value) = 0;
virtual bool Write16(u32 address, u16 value) = 0;
virtual bool Write32(u32 address, u32 value) = 0;
virtual bool Write64(u32 address, u64 value) = 0;
virtual bool Write128(u32 address, u128 value) = 0;
virtual bool WriteBytes(u32 address, void* src, u32 size) = 0;
virtual bool WriteBytes(u32 address, const void* src, u32 size) = 0;
template <MemoryAccessType Value>
bool Write(u32 address, Value value);
bool WriteString(u32 address, std::string_view string);
bool IdempotentWrite8(u32 address, u8 value);
bool IdempotentWrite16(u32 address, u16 value);
bool IdempotentWrite32(u32 address, u32 value);
@@ -51,5 +65,5 @@ public:
template <MemoryAccessType Value>
bool IdempotentWrite(u32 address, Value value);
virtual bool CompareBytes(u32 address, void* src, u32 size) = 0;
virtual bool CompareBytes(u32 address, const void* src, u32 size) = 0;
};
+88 -26
View File
@@ -17,7 +17,7 @@ struct RapidYAMLContext
Error* error = nullptr;
};
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors)
{
RapidYAMLContext context;
context.error = error;
@@ -28,40 +28,93 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
callbacks.set_user_data(static_cast<void*>(&context));
callbacks.set_error_basic([](ryml::csubstr msg, const ryml::ErrorDataBasic& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_basic_format(std::move(callback), msg, errdata);
// We might have already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 1);
}
else
{
std::terminate();
}
});
callbacks.set_error_parse([](ryml::csubstr msg, const ryml::ErrorDataParse& errdata, void* user_data) {
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_parse_format(std::move(callback), msg, errdata);
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
Error::SetString(context->error, std::move(description));
std::longjmp(context->env, 1);
if (context != nullptr)
{
std::longjmp(context->env, 2);
}
else
{
std::terminate();
}
});
callbacks.set_error_visit([](ryml::csubstr msg, const ryml::ErrorDataVisit& errdata, void* user_data) {
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
// This scope needs to stay, so all objects destruct before std::longjump
{
std::string description;
auto callback = [&description](ryml::csubstr string) {
description.append(string.str, string.len);
};
ryml::err_visit_format(std::move(callback), msg, errdata);
// We've probably already returned, so don't try to recover.
pxFailRel(description.c_str());
std::abort();
if (context != nullptr)
{
Error::SetString(context->error, std::move(description));
}
else
{
pxFailRel(description.c_str());
}
}
if (context != nullptr)
{
std::longjmp(context->env, 3);
}
else
{
std::terminate();
}
});
#else
callbacks.m_user_data = static_cast<void*>(&context);
@@ -76,15 +129,24 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
ryml::EventHandlerTree event_handler(callbacks);
ryml::Parser parser(&event_handler);
ryml::Tree tree;
ryml::Tree tree(callbacks);
// The only options RapidYAML provides for recovering from errors are
// throwing an exception or using setjmp/longjmp. Since we have exceptions
// disabled we have to use the latter option.
if (setjmp(context.env))
if (setjmp(context.env) != 0)
return std::nullopt;
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
if (resolve_anchors)
{
tree.resolve();
}
// Callbacks passed to ryml::Tree are used for value parsing errors later,
// so we need to clear the context before it goes out of scope.
callbacks.set_user_data(nullptr);
tree.callbacks(callbacks);
return tree;
}
+3 -1
View File
@@ -14,4 +14,6 @@
/// parsing errors (as is recommended by the documentation for cases where
/// exceptions are disabled). The file_name parameter is only used for error
/// messages, which are returned via the error parameter.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error);
/// If resolve_anchors is set to true, YAML anchors and aliases will be resolved.
/// It's a potentially slow operation, so it's opt-in.
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors = false);
@@ -7,6 +7,8 @@
#include "Debugger/SymbolTree/SymbolTreeModel.h"
#include "Debugger/SymbolTree/TypeString.h"
#include "common/StringUtil.h"
#include <QtWidgets/QCheckBox>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDoubleSpinBox>
@@ -44,12 +46,28 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
QVariant value = node->readValueAsVariant(physical_type, m_cpu, database);
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;;
if (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
!value.isNull())
{
QLineEdit* editor = new QLineEdit(parent);
editor->setText(value.toString());
result = editor;
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
@@ -111,7 +129,7 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti
}
case ccc::ast::ENUM:
{
const ccc::ast::Enum& enumeration = type.as<ccc::ast::Enum>();
const ccc::ast::Enum& enumeration = physical_type.as<ccc::ast::Enum>();
QComboBox* combo_box = new QComboBox(parent);
bool named = false;
@@ -186,12 +204,30 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel*
if (!logical_type)
return;
const ccc::ast::Node& type = *logical_type->physical_type(database).first;
switch (type.descriptor)
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
QLineEdit* line_edit = qobject_cast<QLineEdit*>(editor);
if (array.element_count > 0 &&
element_type.name == "char" &&
node->location.type == SymbolTreeLocation::MEMORY &&
line_edit)
{
value = line_edit->text();
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtin = type.as<ccc::ast::BuiltIn>();
const ccc::ast::BuiltIn& builtin = physical_type.as<ccc::ast::BuiltIn>();
switch (builtin.bclass)
{
+60 -10
View File
@@ -67,7 +67,7 @@ bool SymbolTreeNode::writeToVM(
if (logical_type)
{
const ccc::ast::Node& physical_type = *logical_type->physical_type(database).first;
writeValueFromVariant(m_value, physical_type, cpu);
writeValueFromVariant(m_value, physical_type, cpu, database);
}
data_changed |= updateDisplayString(cpu, database, display_options);
@@ -76,10 +76,29 @@ bool SymbolTreeNode::writeToVM(
return data_changed;
}
QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const
QVariant SymbolTreeNode::readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
std::optional<std::string> string = cpu.ReadString(location.address, 256);
if (string.has_value())
return QString::fromStdString(*string);
}
break;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& builtIn = physical_type.as<ccc::ast::BuiltIn>();
@@ -134,10 +153,36 @@ QVariant SymbolTreeNode::readValueAsVariant(const ccc::ast::Node& physical_type,
return QVariant();
}
bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const
bool SymbolTreeNode::writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const
{
if (value.isNull())
return false;
switch (physical_type.descriptor)
{
case ccc::ast::ARRAY:
{
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
QByteArray byte_array = value.toString().toLatin1();
std::string_view view(
byte_array.data(),
std::min(static_cast<s32>(byte_array.size()), array.element_count - 1));
cpu.WriteString(location.address, view);
break;
}
return false;
}
case ccc::ast::BUILTIN:
{
const ccc::ast::BuiltIn& built_in = physical_type.as<ccc::ast::BuiltIn>();
@@ -191,6 +236,7 @@ bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node&
return false;
}
}
break;
}
case ccc::ast::ENUM:
@@ -266,11 +312,14 @@ QString SymbolTreeNode::generateDisplayString(
const ccc::ast::Array& array = physical_type.as<ccc::ast::Array>();
const ccc::ast::Node& element_type = *array.element_type->physical_type(database).first;
if (element_type.name == "char" && location.type == SymbolTreeLocation::MEMORY)
if (array.element_count > 0 &&
element_type.name == "char" &&
location.type == SymbolTreeLocation::MEMORY)
{
char* string = cpu.stringFromPointer(location.address);
if (string)
return QString("\"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
location.address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
return QString("\"%1\"").arg(*string);
}
QString result;
@@ -412,9 +461,10 @@ QString SymbolTreeNode::generateDisplayString(
if (pointer_or_reference.is_pointer && value_type.name == "char")
{
const char* string = cpu.stringFromPointer(address);
if (string)
result += QString(" \"%1\"").arg(string);
std::optional<std::string> string = cpu.ReadString(
address, 256, MemoryInterface::ALLOW_LONG_STRINGS);
if (string.has_value())
result += QString(" \"%1\"").arg(*string);
}
else if (depth == 0)
{
+11 -2
View File
@@ -62,11 +62,20 @@ public:
const ccc::SymbolDatabase& database,
const SymbolTreeDisplayOptions& display_options);
QVariant readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const;
QVariant readValueAsVariant(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool writeValueFromVariant(
QVariant value,
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
const ccc::SymbolDatabase& database) const;
bool updateDisplayString(
DebugInterface& cpu, const ccc::SymbolDatabase& database, const SymbolTreeDisplayOptions& display);
QString generateDisplayString(
const ccc::ast::Node& physical_type,
DebugInterface& cpu,
+1 -1
View File
@@ -1146,7 +1146,7 @@ void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
{
const QString message =
qApp->translate("QtHost", "RA: Logged in as %1 (%2 pts, softcore: %3 pts). %4 unread messages.")
qApp->translate("QtHost", "RA: Logged in as %1 (%2 pts, Casual: %3 pts). %4 unread messages.")
.arg(QString::fromUtf8(username))
.arg(points)
.arg(sc_points)
+466
View File
@@ -4,10 +4,12 @@
#include "QtUtils.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QLocale>
#include <QtCore/QtGlobal>
#include <QtCore/QMetaObject>
#include <QtCore/QStandardPaths>
#include <QtGui/QAction>
#include <QtGui/QDesktopServices>
#include <QtGui/QIcon>
@@ -15,6 +17,7 @@
#include <QtGui/QPainter>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QLabel>
#include <QtWidgets/QInputDialog>
@@ -27,21 +30,35 @@
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
#include <fmt/format.h>
#ifdef Q_OS_LINUX
#include <QtGui/private/qtx11extras_p.h>
#endif
#include <algorithm>
#include <array>
#include <cstdlib>
#include <map>
#include "common/CocoaTools.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "pcsx2/Config.h"
#include "QtHost.h"
#if defined(_WIN32)
#include "common/RedtapeWindows.h"
#include "common/RedtapeWilCom.h"
#include <Shlobj.h>
#include <shobjidl.h>
#include <comdef.h>
#else
#include <sys/stat.h>
#include <cerrno>
#include <cstring>
#endif
namespace QtUtils
@@ -549,4 +566,453 @@ namespace QtUtils
const QString flag_path = QStringLiteral("%1/icons/flags/%2.svg").arg(QtHost::GetResourcesBasePath()).arg(country_code.toLower());
return QIcon(flag_path);
}
bool IsRunningInFlatpak()
{
// Checks for the existence of the `.flatpak-info` file which seems to be always present inside flatpak sandboxes.
return FileSystem::FileExists("/.flatpak-info");
}
bool IsRunningInAppImage()
{
// The AppImage runtime sets APPIMAGE environment variable so we can check for that.
return std::getenv("APPIMAGE") != nullptr;
}
void CreateShortcut(QWidget* parent, const std::string& name, const std::string& game_path,
std::vector<std::string> passed_cli_args, const std::string& custom_args,
const std::string& icon_path, bool is_desktop, bool prompt_for_destination)
{
const auto tr_msg = [](const char* str) {
return QCoreApplication::translate("CreateShortcut", str);
};
#if defined(_WIN32)
if (name.empty())
{
Console.Error("Cannot create shortcuts without a name.");
return;
}
// Sanitize filename
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::ToNativePath(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), is_desktop ? tr_msg("'Desktop' directory not found") : tr_msg("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Check if the same shortcut already exists
if (prompt_for_destination && FileSystem::FileExists(link_file.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string final_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
if (!custom_args.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += custom_args;
}
if (!clean_path.empty())
{
if (!final_args.empty())
final_args += ' ';
final_args += fmt::format("-- {}", clean_path);
}
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
const auto str_error = [](HRESULT hr) -> std::string {
_com_error err(hr);
const TCHAR* errMsg = err.ErrorMessage();
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
};
// Construct the shortcut
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("CoInitialize failed (%1)").arg(QString::fromStdString(str_error(res))), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
wil::unique_couninitialize_call co_cleanup;
const auto report_error = [&](const QString& reason) {
Console.ErrorFmt("Failed to create shortcut: {}", reason.toStdString());
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
};
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
if (!pShellLink)
{
report_error(tr_msg("CoCreateInstance failed"));
return;
}
// Set path to the executable
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
res = pShellLink->SetPath(target_file.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetPath failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the working directory
const std::wstring working_dir = StringUtil::UTF8StringToWideString(Path::GetDirectory(FileSystem::GetProgramPath()));
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetWorkingDirectory failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the description (shown as the shortcut's tooltip)
const std::wstring description = tr_msg("PlayStation 2 Emulator").toStdWString();
res = pShellLink->SetDescription(description.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetDescription failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Set the launch arguments
if (!final_args.empty())
{
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
res = pShellLink->SetArguments(target_cli_args.c_str());
if (FAILED(res))
{
report_error(tr_msg("SetArguments failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
}
// Set the icon
std::string final_icon_path;
if (!icon_path.empty())
{
final_icon_path = Path::ToNativePath(icon_path);
if (!FileSystem::FileExists(final_icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
else
{
final_icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
}
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(final_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
{
report_error(tr_msg("SetIconLocation failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Use the IPersistFile object to save the shell link
res = pShellLink.query_to(&pPersistFile);
if (FAILED(res))
{
report_error(tr_msg("QueryInterface failed (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
// Save shortcut link to disk
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
if (FAILED(res))
{
report_error(tr_msg("Failed to save the shortcut (%1)").arg(QString::fromStdString(str_error(res))));
return;
}
#else
if (name.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const bool is_flatpak = IsRunningInFlatpak();
// Sanitize filename and game path
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = game_path.empty() ? std::string() : Path::Canonicalize(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the executable path
std::string executable_path = FileSystem::GetPackagePath();
if (executable_path.empty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the destination directory
const QString link_dir = QStandardPaths::writableLocation(is_desktop ? QStandardPaths::DesktopLocation : QStandardPaths::ApplicationsLocation);
if (link_dir.isEmpty())
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not determine the shortcut destination directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
const std::string link_path = fmt::format("{}/{}.desktop", link_dir.toStdString(), clean_name);
std::string icon_name;
if (!icon_path.empty())
{
if (!FileSystem::FileExists(icon_path.c_str()))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
icon_name = icon_path;
}
else
{
if (is_flatpak) // Flatpak
{
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
// Copy PCSX2 icon
icon_name = "PCSX2";
const std::string icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation).toStdString());
const std::string icon_path_dest = fmt::format("{}/{}.png", icon_dest, icon_name);
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path_dest.c_str(), true);
}
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= EscapeShortcutCommandLine(&arg);
if (!is_flatpak)
lossless &= EscapeShortcutCommandLine(&executable_path);
if (!clean_path.empty())
lossless &= EscapeShortcutCommandLine(&clean_path);
if (!lossless)
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
// Assembling the .desktop file
std::string final_args = executable_path;
if (!cmdline.empty())
final_args += fmt::format(" {}", cmdline);
if (!custom_args.empty())
final_args += fmt::format(" {}", custom_args);
if (!clean_path.empty())
final_args += fmt::format(" -- {}", clean_path);
std::string file_content =
"[Desktop Entry]\n"
"Version=1.0\n"
"Type=Application\n"
"Terminal=false\n"
"StartupWMClass=pcsx2-qt\n"
"Exec=" +
final_args + "\n" +
"Name=" +
name + "\n" +
"GenericName=PlayStation 2 Emulator\n"
"Icon=" +
icon_name + "\n" +
"Comment=Sony PlayStation 2 Emulator\n"
"Keywords=game;emulator;\n"
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);
QString final_path = QString::fromStdString(link_path);
if (prompt_for_destination)
{
const QString filter(tr_msg("Desktop Shortcut Files (*.desktop)"));
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, tr_msg("Select Shortcut Save Destination"), final_path, filter));
if (final_path.isEmpty())
return;
}
else
{
const std::string link_dir(Path::GetDirectory(link_path));
if (!link_dir.empty() && !FileSystem::EnsureDirectoryExists(link_dir.c_str(), true))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Could not create the shortcut directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
// Write to .desktop file
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
{
QMessageBox::critical(parent, tr_msg("Failed to create shortcut"), tr_msg("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
#endif
}
bool EscapeShortcutCommandLine(std::string* arg)
{
#ifdef _WIN32
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
return true;
std::string temp;
temp.reserve(arg->length() + 10);
temp += '"';
for (auto it = arg->begin();; ++it)
{
int backslash_count = 0;
while (it != arg->end() && *it == '\\')
{
++it;
++backslash_count;
}
if (it == arg->end())
{
temp.append(backslash_count * 2, '\\');
break;
}
if (*it == '"')
{
temp.append(backslash_count * 2 + 1, '\\');
temp += '"';
}
else
{
temp.append(backslash_count, '\\');
temp += *it;
}
}
temp += '"';
*arg = std::move(temp);
return true;
#else
const char* carg = arg->c_str();
const char* cend = carg + arg->size();
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
if (next == cend)
return true; // No escaping needed, don't modify
bool lossless = true;
std::string temp = "\"";
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
while (true)
{
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
temp.append(carg, next);
carg = next;
if (carg == cend)
break;
switch (*carg)
{
case '"':
case '`':
temp.push_back('\\');
temp.push_back(*carg);
break;
case '\\':
temp.append("\\\\\\\\");
break;
case '$':
temp.push_back('\\');
temp.push_back('\\');
temp.push_back(*carg);
break;
case '%':
temp.push_back('%');
temp.push_back(*carg);
break;
default:
temp.push_back(' ');
lossless = false;
break;
}
++carg;
}
temp.push_back('"');
*arg = std::move(temp);
return lossless;
#endif
}
} // namespace QtUtils
+17
View File
@@ -17,9 +17,11 @@
#include <QtGui/QScreen>
#include <functional>
#include <initializer_list>
#include <string>
#include <string_view>
#include <type_traits>
#include <optional>
#include <vector>
#include "common/Console.h"
@@ -209,4 +211,19 @@ namespace QtUtils
/// Gets a flag icon for a given language code
/// Returns an empty QIcon if no flag is available for the language
QIcon GetFlagIconForLanguage(const QString& language_code);
/// Returns true if PCSX2 is running inside a Flatpak sandbox.
bool IsRunningInFlatpak();
/// Returns true if PCSX2 is running from an AppImage.
bool IsRunningInAppImage();
/// Creates a desktop or launcher (Start Menu / applications) shortcuts
void CreateShortcut(QWidget* parent, const std::string& name, const std::string& game_path,
std::vector<std::string> passed_cli_args, const std::string& custom_args,
const std::string& icon_path, bool is_desktop, bool prompt_for_destination = true);
/// Escapes the given string for use as a shortcut command line argument.
/// Returns whether the escaping operation was lossless.
bool EscapeShortcutCommandLine(std::string* arg);
} // namespace QtUtils
@@ -365,7 +365,6 @@
<tabstop>gsDumpCompression</tabstop>
<tabstop>texturePreloading</tabstop>
<tabstop>exclusiveFullscreenControl</tabstop>
<tabstop>rov</tabstop>
<tabstop>extendedUpscales</tabstop>
<tabstop>spinGPUDuringReadbacks</tabstop>
<tabstop>spinCPUDuringReadbacks</tabstop>
+22
View File
@@ -88,6 +88,11 @@ void SetupWizardDialog::nextPage()
const int current_page = m_ui.pages->currentIndex();
if (current_page == Page_Complete)
{
if (m_ui.createDesktopShortcut->isChecked())
QtUtils::CreateShortcut(this, "PCSX2", std::string(), {}, std::string(), std::string(), true, false);
if (m_ui.addToApplicationMenu->isChecked())
QtUtils::CreateShortcut(this, "PCSX2", std::string(), {}, std::string(), std::string(), false, false);
accept();
return;
}
@@ -177,6 +182,7 @@ void SetupWizardDialog::setupUi()
setupGameListPage();
setupControllerPage();
setupRetroAchievementsPage();
setupCompletePage();
}
void SetupWizardDialog::setupLanguagePage()
@@ -435,6 +441,22 @@ void SetupWizardDialog::setupRetroAchievementsPage()
refreshRetroAchievementsLoginState();
}
void SetupWizardDialog::setupCompletePage()
{
#if defined(_WIN32)
const bool can_create_shortcuts = true;
m_ui.addToApplicationMenu->setText(tr("Add PCSX2 to the Start Menu"));
#elif defined(__linux__)
// Only offer shortcuts for the AppImage since Flatpak creates its own launcher, and
// third-party builds (AUR, COPR, etc.) ship their own .desktop file.
const bool can_create_shortcuts = QtUtils::IsRunningInAppImage();
#else
const bool can_create_shortcuts = false;
#endif
m_ui.createDesktopShortcut->setVisible(can_create_shortcuts);
m_ui.addToApplicationMenu->setVisible(can_create_shortcuts);
}
void SetupWizardDialog::refreshRetroAchievementsLoginState()
{
const std::string username(Host::GetBaseStringSettingValue("Achievements", "Username"));
+1
View File
@@ -66,6 +66,7 @@ private:
void setupGameListPage();
void setupControllerPage();
void setupRetroAchievementsPage();
void setupCompletePage();
void refreshRetroAchievementsLoginState();
void pageChangedTo(int page);
+47 -25
View File
@@ -23,7 +23,7 @@
<number>10</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<layout class="QVBoxLayout" name="pagesLayout">
<item>
<widget class="QLabel" name="logo">
<property name="sizePolicy">
@@ -61,7 +61,6 @@
<widget class="QLabel" name="labelLanguage">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
@@ -127,7 +126,7 @@
<property name="currentIndex">
<number>4</number>
</property>
<widget class="QWidget" name="page">
<widget class="QWidget" name="SetupWizardPages">
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>4</number>
@@ -145,7 +144,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="IntroLabel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Welcome to PCSX2!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;This wizard will help guide you through the configuration steps required to use the application. It is recommended if this is your first time installing PCSX2 that you view the setup guide &lt;a href=&quot;https://pcsx2.net/docs/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;By default, PCSX2 will connect to the server at &lt;a href=&quot;https://pcsx2.net/&quot;&gt;pcsx2.net&lt;/a&gt; to check for updates, and if available and confirmed, download update packages from &lt;a href=&quot;https://github.com/&quot;&gt;github.com&lt;/a&gt;. If you do not wish for PCSX2 to make any network connections on startup, you should uncheck the Automatic Updates option now. The Automatic Update setting can be changed later at any time in Interface Settings.&lt;/p&gt;&lt;p&gt;Please choose a language and theme to begin.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -164,7 +163,7 @@
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<layout class="QFormLayout" name="languageLayout">
<item row="0" column="0">
<widget class="QLabel" name="languageLabel">
<property name="text">
@@ -222,7 +221,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<widget class="QWidget" name="pageBios">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
@@ -237,7 +236,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_4">
<widget class="QLabel" name="biosIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 requires a PS2 BIOS in order to run.&lt;/p&gt;&lt;p&gt;For legal reasons, you must obtain a BIOS &lt;strong&gt;from an actual PS2 unit that you own&lt;/strong&gt; (borrowing doesn't count).&lt;/p&gt;&lt;p&gt;Once dumped, this BIOS image should be placed in the bios folder within the data directory shown below, or you can instruct PCSX2 to scan an alternative directory.&lt;/p&gt;&lt;p&gt;A guide for dumping your BIOS can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/bios/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -256,7 +255,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QHBoxLayout" name="biosPathLayout">
<item>
<widget class="QLabel" name="biosSearchDirectoryLabel">
<property name="text">
@@ -310,7 +309,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<layout class="QHBoxLayout" name="biosActions">
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
@@ -342,7 +341,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<widget class="QWidget" name="pageDumps">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
@@ -357,7 +356,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<widget class="QLabel" name="dumpsIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;PCSX2 will automatically scan and identify games from the selected directories below, and populate the game list.&lt;br&gt;These games should be dumped from discs you own. Guides for dumping discs can be found &lt;a href=&quot;https://pcsx2.net/docs/setup/dumping&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Supported formats for dumps include:&lt;/p&gt;&lt;p&gt;&lt;ul&gt;&lt;li&gt;.bin/.iso (ISO Disc Images)&lt;/li&gt;&lt;li&gt;.mdf (Media Descriptor File)&lt;/li&gt;&lt;li&gt;.chd (Compressed Hunks of Data)&lt;/li&gt;&lt;li&gt;.cso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.zso (Compressed ISO)&lt;/li&gt;&lt;li&gt;.gz (Gzip Compressed ISO)&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -376,7 +375,7 @@
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<layout class="QHBoxLayout" name="dumpsLayout">
<item>
<widget class="QLabel" name="searchDirectoryListLabel">
<property name="text">
@@ -474,7 +473,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_8">
<widget class="QLabel" name="controllerIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, PCSX2 will map your keyboard to the virtual PS2 controller.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:704;&quot;&gt;To use an external controller, you must map it first. &lt;/span&gt;On this screen, you can automatically map any controller which is currently connected. If your controller is not currently connected, you can plug it in now.&lt;/p&gt;&lt;p&gt;To change controller bindings in more detail, or use multi-tap, open the Settings menu and choose Controllers once you have completed the Setup Wizard.&lt;/p&gt;&lt;p&gt;Guides for configuring controllers can be found &lt;a href=&quot;https://pcsx2.net/docs/post/controllers/&quot;&gt;&lt;span style=&quot;&quot;&gt;here&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -487,7 +486,7 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="controller1Group">
<property name="title">
<string>Controller Port 1</string>
</property>
@@ -496,14 +495,14 @@
<widget class="QComboBox" name="controller1Type"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_12">
<widget class="QLabel" name="controllerMappedLabel">
<property name="text">
<string>Controller Mapped To:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<widget class="QLabel" name="controller1Label">
<property name="text">
<string>Controller Type:</string>
</property>
@@ -513,12 +512,15 @@
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<layout class="QHBoxLayout" name="controller1MappingLayout">
<item>
<widget class="QLabel" name="controller1Mapping">
<property name="text">
<string>Default (Keyboard)</string>
</property>
<property name="buddy">
<cstring>controller1Type</cstring>
</property>
</widget>
</item>
<item>
@@ -554,13 +556,13 @@
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<widget class="QGroupBox" name="controller2Group">
<property name="title">
<string>Controller Port 2</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_13">
<widget class="QLabel" name="controller2Label">
<property name="text">
<string>Controller Type:</string>
</property>
@@ -573,19 +575,22 @@
<widget class="QComboBox" name="controller2Type"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_14">
<widget class="QLabel" name="controller2MappedLabel">
<property name="text">
<string>Controller Mapped To:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<layout class="QHBoxLayout" name="controller2MappingLayout">
<item>
<widget class="QLabel" name="controller2Mapping">
<property name="text">
<string>Default (Keyboard)</string>
</property>
<property name="buddy">
<cstring>controller2Type</cstring>
</property>
</widget>
</item>
<item>
@@ -653,7 +658,7 @@
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<layout class="QHBoxLayout" name="raLogoLayout">
<item>
<widget class="QLabel" name="raLogo">
<property name="sizePolicy">
@@ -736,6 +741,9 @@
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="buddy">
<cstring>raEnableAchievements</cstring>
</property>
</widget>
</item>
<item>
@@ -885,7 +893,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_5">
<widget class="QWidget" name="pageComplete">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="leftMargin">
<number>0</number>
@@ -900,7 +908,7 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_9">
<widget class="QLabel" name="completeIntro">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;h1 style=&quot; margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-size:xx-large; font-weight:700;&quot;&gt;Setup Complete!&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;You are now ready to run games.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:700;&quot;&gt;Warning: Do not run untrusted programs in PCSX2. It is not a sandbox and cannot protect your computer from malicious software.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Further options are available under the settings menu. You can also use the Big Picture UI for navigation entirely with a gamepad.&lt;/p&gt;&lt;p&gt;We hope you enjoy using PCSX2.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -912,12 +920,26 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="createDesktopShortcut">
<property name="text">
<string>Create Desktop shortcut</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="addToApplicationMenu">
<property name="text">
<string>Add PCSX2 to the Application Menu</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="navigationLayout">
<property name="spacing">
<number>6</number>
</property>
+4 -433
View File
@@ -3,23 +3,14 @@
#include "ShortcutCreationDialog.h"
#include "QtHost.h"
#include <fmt/format.h>
#include "QtUtils.h"
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QMessageBox>
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "VMManager.h"
#if defined(_WIN32)
#include "common/RedtapeWilCom.h"
#include <shlobj.h>
#include <shobjidl.h>
#include <comdef.h>
#endif
ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path)
: QDialog(parent)
, m_title(title)
@@ -100,7 +91,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
m_ui.resetIconButton->setEnabled(false);
});
if (std::getenv("container"))
if (QtUtils::IsRunningInFlatpak())
{
m_ui.portableModeToggle->setEnabled(false);
m_ui.shortcutDesktop->setEnabled(false);
@@ -108,7 +99,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
}
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() {
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [this]() {
std::vector<std::string> args;
if (m_ui.portableModeToggle->isChecked())
@@ -162,430 +153,10 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
std::string custom_args = m_ui.customArgsInput->text().toStdString();
std::string icon_path = m_ui.iconPath->text().toStdString();
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, icon_path, m_ui.shortcutDesktop->isChecked());
QtUtils::CreateShortcut(this, m_title.toStdString(), m_path.toStdString(), std::move(args), custom_args, icon_path, m_ui.shortcutDesktop->isChecked());
accept();
});
}
void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, const std::string icon_path, bool is_desktop)
{
#if defined(_WIN32)
if (name.empty())
{
Console.Error("Cannot create shortcuts without a name.");
return;
}
// Sanitize filename
const std::string clean_name = Path::SanitizeFileName(name).c_str();
std::string clean_path = Path::ToNativePath(Path::RealPath(game_path)).c_str();
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), is_desktop ? tr("'Desktop' directory not found") : tr("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Check if the same shortcut already exists
if (FileSystem::FileExists(link_file.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
std::string final_args = fmt::format("{} {} -- {}", combined_args, custom_args, clean_path);
Console.WriteLnFmt("Creating a shortcut '{}' with arguments '{}'", link_file, final_args);
const auto str_error = [](HRESULT hr) -> std::string {
_com_error err(hr);
const TCHAR* errMsg = err.ErrorMessage();
return fmt::format("{} [{}]", StringUtil::WideStringToUTF8String(errMsg), hr);
};
// Construct the shortcut
// https://stackoverflow.com/questions/3906974/how-to-programmatically-create-a-shortcut-using-win32
HRESULT res = CoInitialize(NULL);
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1)").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
wil::unique_couninitialize_call co_cleanup;
const auto report_error = [&](const QString& reason) {
Console.ErrorFmt("Failed to create shortcut: {}", reason.toStdString());
QMessageBox::critical(this, tr("Failed to create shortcut"), reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
};
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
if (!pShellLink)
{
report_error(tr("CoCreateInstance failed"));
return;
}
// Set path to the executable
const std::wstring target_file = StringUtil::UTF8StringToWideString(FileSystem::GetProgramPath());
res = pShellLink->SetPath(target_file.c_str());
if (FAILED(res))
{
report_error(tr("SetPath failed (%1)").arg(str_error(res)));
return;
}
// Set the working directory
const std::wstring working_dir = StringUtil::UTF8StringToWideString(FileSystem::GetWorkingDirectory());
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
if (FAILED(res))
{
report_error(tr("SetWorkingDirectory failed (%1)").arg(str_error(res)));
return;
}
// Set the launch arguments
if (!final_args.empty())
{
const std::wstring target_cli_args = StringUtil::UTF8StringToWideString(final_args);
res = pShellLink->SetArguments(target_cli_args.c_str());
if (FAILED(res))
{
report_error(tr("SetArguments failed (%1)").arg(str_error(res)));
return;
}
}
// Set the icon
std::string final_icon_path;
if (!icon_path.empty())
{
final_icon_path = Path::ToNativePath(icon_path);
if (!FileSystem::FileExists(final_icon_path.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
}
else
{
final_icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
}
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(final_icon_path);
res = pShellLink->SetIconLocation(w_icon_path.c_str(), 0);
if (FAILED(res))
{
report_error(tr("SetIconLocation failed (%1)").arg(str_error(res)));
return;
}
// Use the IPersistFile object to save the shell link
res = pShellLink.query_to(&pPersistFile);
if (FAILED(res))
{
report_error(tr("QueryInterface failed (%1)").arg(str_error(res)));
return;
}
// Save shortcut link to disk
const std::wstring w_link_file = StringUtil::UTF8StringToWideString(link_file);
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
if (FAILED(res))
{
report_error(tr("Failed to save the shortcut (%1)").arg(str_error(res)));
return;
}
#else
if (name.empty())
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Cannot create a shortcut without a title."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
bool is_flatpak = (std::getenv("container"));
// Sanitize filename and game path
const std::string clean_name = Path::SanitizeFileName(name);
std::string clean_path = Path::Canonicalize(Path::RealPath(game_path));
if (!Path::IsValidFileName(clean_name))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Filename contains illegal character."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find the executable path
std::string executable_path = FileSystem::GetPackagePath();
if (executable_path.empty())
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Executable path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Find home directory
std::string link_path;
const char* home = std::getenv("HOME");
const char* xdg_desktop_dir = std::getenv("XDG_DESKTOP_DIR");
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
if (home)
{
if (is_desktop)
{
if (xdg_desktop_dir)
link_path = fmt::format("{}/{}.desktop", xdg_desktop_dir, clean_name);
else
link_path = fmt::format("{}/Desktop/{}.desktop", home, clean_name);
}
else
{
if (xdg_data_home)
link_path = fmt::format("{}/applications/{}.desktop", xdg_data_home, clean_name);
else
link_path = fmt::format("{}/.local/share/applications/{}.desktop", home, clean_name);
}
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Path to the Home directory is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string icon_name;
if (!icon_path.empty())
{
if (!FileSystem::FileExists(icon_path.c_str()))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("The selected icon file does not exist."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
icon_name = icon_path;
}
else
{
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
if (is_flatpak) // Flatpak
{
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
icon_name = "PCSX2";
std::string icon_path_dest = fmt::format("{}/{}.png", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
if (!FileSystem::FileExists(icon_path_dest.c_str()))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path_dest.c_str(), false);
}
}
// Shortcut CmdLine Args
bool lossless = true;
for (std::string& arg : passed_cli_args)
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
// Further string sanitization
if (!is_flatpak)
ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path);
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
// Assembling the .desktop file
std::string final_args;
final_args = fmt::format("{} {} {} -- {}", executable_path, cmdline, custom_args, clean_path);
std::string file_content =
"[Desktop Entry]\n"
"Encoding=UTF-8\n"
"Version=1.0\n"
"Type=Application\n"
"Terminal=false\n"
"StartupWMClass=PCSX2\n"
"Exec=" +
final_args + "\n" +
"Name=" +
clean_name + "\n" +
"Icon=" +
icon_name + "\n" +
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);
// Prompt user for shortcut saving destination
QString final_path(QStringLiteral("%1").arg(QString::fromStdString(link_path)));
const QString filter(tr("Desktop Shortcut Files (*.desktop)"));
final_path = QDir::toNativeSeparators(QFileDialog::getSaveFileName(this, tr("Select Shortcut Save Destination"), final_path, filter));
if (final_path.isEmpty())
return;
// Write to .desktop file
if (!FileSystem::WriteStringToFile(final_path.toStdString().c_str(), sv))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Failed to create .desktop file"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
if (chmod(final_path.toStdString().c_str(), S_IRWXU) != 0) // enables user to execute file
Console.ErrorFmt("Failed to change file permissions for .desktop file: {} ({})", strerror(errno), errno);
#endif
}
bool ShortcutCreationDialog::EscapeShortcutCommandLine(std::string* arg)
{
#ifdef _WIN32
if (!arg->empty() && arg->find_first_of(" \t\n\v\"") == std::string::npos)
return true;
std::string temp;
temp.reserve(arg->length() + 10);
temp += '"';
for (auto it = arg->begin();; ++it)
{
int backslash_count = 0;
while (it != arg->end() && *it == '\\')
{
++it;
++backslash_count;
}
if (it == arg->end())
{
temp.append(backslash_count * 2, '\\');
break;
}
if (*it == '"')
{
temp.append(backslash_count * 2 + 1, '\\');
temp += '"';
}
else
{
temp.append(backslash_count, '\\');
temp += *it;
}
}
temp += '"';
*arg = std::move(temp);
return true;
#else
const char* carg = arg->c_str();
const char* cend = carg + arg->size();
const char* RESERVED_CHARS = " \t\n\\\"'\\\\><~|%&;$*?#()`"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
const char* next = carg + std::strcspn(carg, RESERVED_CHARS);
if (next == cend)
return true; // No escaping needed, don't modify
bool lossless = true;
std::string temp = "\"";
const char* NOT_VALID_IN_QUOTE = "%`$\"\\\n"
"\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0d\x0e\x0f"
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f";
while (true)
{
next = carg + std::strcspn(carg, NOT_VALID_IN_QUOTE);
temp.append(carg, next);
carg = next;
if (carg == cend)
break;
switch (*carg)
{
case '"':
case '`':
temp.push_back('\\');
temp.push_back(*carg);
break;
case '\\':
temp.append("\\\\\\\\");
break;
case '$':
temp.push_back('\\');
temp.push_back('\\');
temp.push_back(*carg);
break;
case '%':
temp.push_back('%');
temp.push_back(*carg);
break;
default:
temp.push_back(' ');
lossless = false;
break;
}
++carg;
}
temp.push_back('"');
*arg = std::move(temp);
return lossless;
#endif
}
#include "moc_ShortcutCreationDialog.cpp"
-7
View File
@@ -14,13 +14,6 @@ public:
ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path);
~ShortcutCreationDialog() = default;
/// Create desktop shortcut for games
void CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, const std::string icon_path, bool is_desktop);
/// Escapes the given string for use with command line arguments.
/// Returns a bool that indicates whether the escaping operation are lossless or not.
bool EscapeShortcutCommandLine(std::string* cmdline);
protected:
const QString m_title;
const QString m_path;
File diff suppressed because it is too large Load Diff
+35 -24
View File
@@ -183,7 +183,7 @@ namespace Achievements
static void DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& entry, bool is_self, float rank_column_width,
float name_column_width, float time_column_width, float column_spacing);
static void OpenSubset(const rc_client_subset_t* subset);
static bool DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only);
static bool DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool& focus_sidebar, bool leaderboards_only);
static void DrawSubsetSidebarFooter(bool sidebar_has_focus);
static void OpenLeaderboard(const rc_client_leaderboard_t* lboard);
static void LeaderboardFetchNearbyCallback(
@@ -1915,7 +1915,7 @@ void Achievements::ShowLoginSuccess(const rc_client_t* client)
//: Summary for login notification.
std::string title = user->display_name;
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {0} pts (softcore: {1} pts)\nUnread messages: {2}"), user->score,
std::string summary = fmt::format(TRANSLATE_FS("Achievements", "Score: {0} pts (Casual: {1} pts)\nUnread messages: {2}"), user->score,
user->score_softcore, user->num_unread_messages);
MTGS::RunOnGSThread([title = std::move(title), summary = std::move(summary), badge_path = std::move(badge_path)]() {
@@ -2437,7 +2437,7 @@ bool Achievements::PrepareAchievementsWindow()
return true;
}
bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool leaderboards_only)
bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focus, bool& focus_sidebar, bool leaderboards_only)
{
if (!s_subset_list)
return false;
@@ -2452,7 +2452,7 @@ bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focu
bool subset_selected = false;
if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), 0, 0))
if (ImGui::BeginChild("subset_sidebar", ImVec2(sidebar_width, 0.0f), ImGuiChildFlags_NavFlattened, 0))
{
sidebar_has_focus = ImGui::IsWindowFocused();
ImGuiFullscreen::BeginMenuButtons();
@@ -2475,6 +2475,11 @@ bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focu
ImGuiFullscreen::MenuButtonFrame(id_str, true, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
&visible, &hovered, &bb.Min, &bb.Max, 0, 1.0f);
if (focus_sidebar && ((s_open_subset && s_open_subset->id == subset->id) || (!s_open_subset && i == 0)))
{
ImGui::SetItemDefaultFocus();
}
if (s_open_subset && s_open_subset->id == subset->id)
{
ImGui::GetWindowDrawList()->AddRectFilled(
@@ -2517,6 +2522,10 @@ bool Achievements::DrawSubsetSidebar(float sidebar_width, bool& sidebar_has_focu
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
ImGui::SameLine();
ImGui::PopStyleVar();
if (focus_sidebar)
focus_sidebar = false;
return subset_selected;
}
@@ -2538,7 +2547,8 @@ void Achievements::DrawSubsetSidebarFooter(bool sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Subsets")),
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Subsets")),
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
});
}
}
@@ -2556,7 +2566,8 @@ void Achievements::DrawSubsetSidebarFooter(bool sidebar_has_focus)
{
ImGuiFullscreen::SetFullscreenFooterText(std::array{
std::make_pair(ICON_PF_ARROW_UP ICON_PF_ARROW_DOWN, TRANSLATE_SV("Achievements", "Change Selection")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Subsets")),
std::make_pair(ICON_PF_ARROW_LEFT, TRANSLATE_SV("Achievements", "Subsets")),
std::make_pair(ICON_PF_ESC, TRANSLATE_SV("Achievements", "Back")),
});
}
}
@@ -2627,16 +2638,11 @@ void Achievements::DrawAchievementsWindow()
const bool wants_close = ImGuiFullscreen::WantsToCloseMenu();
if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
(wants_close && (!has_multiple_subsets || sidebar_has_focus)))
wants_close)
{
sidebar_has_focus = false;
FullscreenUI::ReturnToPreviousWindow();
}
else if (has_multiple_subsets && !sidebar_has_focus && wants_close)
{
sidebar_has_focus = true;
focus_sidebar = true;
}
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + GetLineHeight(g_large_font)));
text.assign(s_open_subset ? s_open_subset->title : s_game_title);
@@ -2731,9 +2737,8 @@ void Achievements::DrawAchievementsWindow()
if (focus_sidebar)
{
ImGui::SetNextWindowFocus();
focus_sidebar = false;
}
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, false))
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, focus_sidebar, false))
ImGui::SetNextWindowFocus();
}
@@ -2744,8 +2749,14 @@ void Achievements::DrawAchievementsWindow()
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), 0, 0))
if (ImGui::BeginChild("achievements_content", ImVec2(content_width, 0.0f), ImGuiChildFlags_NavFlattened, 0))
{
if (has_multiple_subsets && !sidebar_has_focus &&
(ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, false) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false)))
{
sidebar_has_focus = true;
focus_sidebar = true;
}
static const char* bucket_names[NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS] = {
TRANSLATE_NOOP("Achievements", "Unknown"),
@@ -3152,16 +3163,11 @@ void Achievements::DrawLeaderboardsWindow()
{
const bool wants_close = ImGuiFullscreen::WantsToCloseMenu();
if (ImGuiFullscreen::FloatingButton(ICON_FA_SQUARE_XMARK, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true, g_large_font) ||
(wants_close && (!has_multiple_subsets || sidebar_has_focus)))
wants_close)
{
sidebar_has_focus = false;
FullscreenUI::ReturnToPreviousWindow();
}
else if (has_multiple_subsets && !sidebar_has_focus && wants_close)
{
sidebar_has_focus = true;
focus_sidebar = true;
}
}
else
{
@@ -3330,9 +3336,8 @@ void Achievements::DrawLeaderboardsWindow()
if (focus_sidebar)
{
ImGui::SetNextWindowFocus();
focus_sidebar = false;
}
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, true))
if (DrawSubsetSidebar(sidebar_width, sidebar_has_focus, focus_sidebar, true))
ImGui::SetNextWindowFocus();
}
@@ -3343,8 +3348,14 @@ void Achievements::DrawLeaderboardsWindow()
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryColor));
ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
if (ImGui::BeginChild("leaderboards_content", ImVec2(content_width, 0.0f), 0, 0))
if (ImGui::BeginChild("leaderboards_content", ImVec2(content_width, 0.0f), ImGuiChildFlags_NavFlattened, 0))
{
if (has_multiple_subsets && !sidebar_has_focus &&
(ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, false) || ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false)))
{
sidebar_has_focus = true;
focus_sidebar = true;
}
if (s_restore_leaderboard_scroll)
{
+3 -3
View File
@@ -376,9 +376,11 @@ bool TAPGetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter,
HRESULT cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (cohr == RPC_E_CHANGED_MODE)
cohr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (!SUCCEEDED(cohr))
if (FAILED(cohr))
return false;
wil::unique_couninitialize_call uninit;
PIP_ADAPTER_ADDRESSES bridgeAdapter = nullptr;
//Create Instance of INetCfg
@@ -472,8 +474,6 @@ bool TAPGetWin32Adapter(const std::string& name, PIP_ADAPTER_ADDRESSES adapter,
}
}
CoUninitialize();
if (bridgeAdapter != nullptr)
{
*adapter = *bridgeAdapter;
+6 -37
View File
@@ -63,37 +63,6 @@ void DebugInterface::resumeCpu()
VMManager::SetPaused(false);
}
char* DebugInterface::stringFromPointer(u32 p)
{
const int BUFFER_LEN = 64;
static char buf[BUFFER_LEN] = {0};
if (!isValidAddress(p))
return NULL;
// This is going to blow up if it hits a TLB miss..
// Hopefully the checks in isValidAddress() are sufficient.
for (u32 i = 0; i < BUFFER_LEN; i++)
{
char c = Read8(p + i);
buf[i] = c;
if (c == 0)
{
return i > 0 ? buf : NULL;
}
else if (c < 0x20 || c >= 0x7f)
{
// non printable character
return NULL;
}
}
buf[BUFFER_LEN - 1] = 0;
buf[BUFFER_LEN - 2] = '~';
return buf;
}
std::optional<u32> DebugInterface::getCallerStackPointer(const ccc::Function& currentFunction)
{
u32 sp = getRegister(EECAT_GPR, 29);
@@ -336,12 +305,12 @@ bool R5900DebugInterface::Write128(u32 address, u128 value)
return isValidAddress(address) && vtlb_memSafeWriteBytes(address, &value, sizeof(value));
}
bool R5900DebugInterface::WriteBytes(u32 address, void* src, u32 size)
bool R5900DebugInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeWriteBytes(address, src, size);
}
bool R5900DebugInterface::CompareBytes(u32 address, void* src, u32 size)
bool R5900DebugInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return vtlb_memSafeCmpBytes(address, src, size) == 0;
}
@@ -839,12 +808,12 @@ bool R3000DebugInterface::Write128(u32 address, u128 value)
return true;
}
bool R3000DebugInterface::WriteBytes(u32 address, void* src, u32 size)
bool R3000DebugInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeWriteBytes(address, src, size);
}
bool R3000DebugInterface::CompareBytes(u32 address, void* src, u32 size)
bool R3000DebugInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeCmpBytes(address, src, size) == 0;
}
@@ -1185,12 +1154,12 @@ bool ElfMemoryReader::Write128(u32 address, u128 value)
return false;
}
bool ElfMemoryReader::WriteBytes(u32 address, void* src, u32 size)
bool ElfMemoryReader::WriteBytes(u32 address, const void* src, u32 size)
{
return false;
}
bool ElfMemoryReader::CompareBytes(u32 address, void* src, u32 size)
bool ElfMemoryReader::CompareBytes(u32 address, const void* src, u32 size)
{
std::optional<std::span<const u8>> bytes = m_elf.get_virtual(address, size);
if (!bytes.has_value())
+6 -7
View File
@@ -80,7 +80,6 @@ public:
bool isCpuPaused();
void pauseCpu();
void resumeCpu();
char* stringFromPointer(u32 p);
std::optional<u32> getCallerStackPointer(const ccc::Function& currentFunction);
std::optional<u32> getStackFrameSize(const ccc::Function& currentFunction);
@@ -115,9 +114,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
// register stuff
int getRegisterCategoryCount() override;
@@ -161,9 +160,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
// register stuff
int getRegisterCategoryCount() override;
@@ -210,9 +209,9 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
protected:
const ccc::ElfFile& m_elf;
+59 -58
View File
@@ -502,68 +502,67 @@ void GSDevice::UpdateImGuiTextures()
case ImTextureStatus_Destroyed:
continue;
case ImTextureStatus_WantCreate:
{
GSTexture* gs_tex = g_gs_device->CreateTexture(im_tex->Width, im_tex->Height, 1, GSTexture::Format::Color);
if (!gs_tex)
pxFailRel("Failed to create ImGui texture");
im_tex->SetTexID(reinterpret_cast<ImTextureID>(gs_tex->GetNativeHandle()));
im_tex->BackendUserData = gs_tex;
[[fallthrough]];
}
case ImTextureStatus_WantUpdates:
{
// If we fell through from WantCreate, then we are uploading the full size
// Otherwise, we are just updating the specified region
// clange-format off
const int upload_x = (im_tex->Status == ImTextureStatus_WantCreate) ? 0 : im_tex->UpdateRect.x;
const int upload_y = (im_tex->Status == ImTextureStatus_WantCreate) ? 0 : im_tex->UpdateRect.y;
const int upload_w = (im_tex->Status == ImTextureStatus_WantCreate) ? im_tex->Width : im_tex->UpdateRect.w;
const int upload_h = (im_tex->Status == ImTextureStatus_WantCreate) ? im_tex->Height : im_tex->UpdateRect.h;
const int upload_pitch = upload_w * im_tex->BytesPerPixel;
// clange-format on
const GSVector4i rect{
upload_x,
upload_y,
upload_x + upload_w,
upload_y + upload_h,
};
GSTexture* gs_tex = static_cast<GSTexture*>(im_tex->BackendUserData);
GSTexture::GSMap map;
if (gs_tex->Map(map, &rect))
if (GSTexture* gs_tex = g_gs_device->CreateTexture(im_tex->Width, im_tex->Height, 1, GSTexture::Format::Color))
{
for (int y = 0; y < upload_h; y++)
std::memcpy(map.bits + map.pitch * y, im_tex->GetPixelsAt(rect.x, rect.y + y), upload_pitch);
gs_tex->Unmap();
im_tex->SetTexID(reinterpret_cast<ImTextureID>(gs_tex->GetNativeHandle()));
im_tex->BackendUserData = gs_tex;
}
else
{
for (int y = 0; y < upload_h; y++)
gs_tex->Update({rect.left, rect.top + y, rect.right, rect.top + y + 1},
im_tex->GetPixelsAt(rect.x, rect.y + y), upload_pitch);
}
im_tex->Status = ImTextureStatus_OK;
break;
}
case ImTextureStatus_WantDestroy:
{
GSTexture* gs_tex = static_cast<GSTexture*>(im_tex->BackendUserData);
if (gs_tex == nullptr)
pxFailRel("Failed to create ImGui texture");
break;
}
[[fallthrough]];
case ImTextureStatus_WantUpdates:
if (GSTexture* gs_tex = static_cast<GSTexture*>(im_tex->BackendUserData))
{
// If we fell through from WantCreate, then we are uploading the full size
// Otherwise, we are just updating the specified region
// clange-format off
const int upload_x = (im_tex->Status == ImTextureStatus_WantCreate) ? 0 : im_tex->UpdateRect.x;
const int upload_y = (im_tex->Status == ImTextureStatus_WantCreate) ? 0 : im_tex->UpdateRect.y;
const int upload_w = (im_tex->Status == ImTextureStatus_WantCreate) ? im_tex->Width : im_tex->UpdateRect.w;
const int upload_h = (im_tex->Status == ImTextureStatus_WantCreate) ? im_tex->Height : im_tex->UpdateRect.h;
const int upload_pitch = upload_w * im_tex->BytesPerPixel;
// clange-format on
// While it's unlikely we're going to reuse the same size as imgui for rendering,
// imgui may request a new atlas of the same size if old font sizes are evicted.
Recycle(gs_tex);
const GSVector4i rect{
upload_x,
upload_y,
upload_x + upload_w,
upload_y + upload_h,
};
im_tex->SetTexID(ImTextureID_Invalid);
im_tex->BackendUserData = nullptr;
im_tex->Status = ImTextureStatus_Destroyed;
GSTexture::GSMap map;
if (gs_tex->Map(map, &rect))
{
for (int y = 0; y < upload_h; y++)
std::memcpy(map.bits + map.pitch * y, im_tex->GetPixelsAt(rect.x, rect.y + y), upload_pitch);
gs_tex->Unmap();
}
else
{
for (int y = 0; y < upload_h; y++)
gs_tex->Update({rect.left, rect.top + y, rect.right, rect.top + y + 1},
im_tex->GetPixelsAt(rect.x, rect.y + y), upload_pitch);
}
im_tex->Status = ImTextureStatus_OK;
}
break;
case ImTextureStatus_WantDestroy:
if (GSTexture* gs_tex = static_cast<GSTexture*>(im_tex->BackendUserData))
{
// While it's unlikely we're going to reuse the same size as imgui for rendering,
// imgui may request a new atlas of the same size if old font sizes are evicted.
Recycle(gs_tex);
im_tex->SetTexID(ImTextureID_Invalid);
im_tex->BackendUserData = nullptr;
im_tex->Status = ImTextureStatus_Destroyed;
}
break;
}
default:
pxAssert(false);
break;
@@ -1111,10 +1110,12 @@ void GSDevice::BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea)
// Create a temporary RT and copy the area needed for the draw.
const int w = ds->GetWidth();
const int h = ds->GetHeight();
m_ds_as_rt = g_gs_device->CreateFeedbackTarget(w, h, GSTexture::Format::DepthColor, false, true);
const GSVector4 dRect(drawarea);
const GSVector4 sRect(dRect.x / w, dRect.y / h, dRect.z / w, dRect.w / h);
StretchRectAuto(ds, sRect, m_ds_as_rt, dRect, Nearest);
if ((m_ds_as_rt = g_gs_device->CreateFeedbackTarget(w, h, GSTexture::Format::DepthColor, false, true)))
{
const GSVector4 dRect(drawarea);
const GSVector4 sRect(dRect.x / w, dRect.y / h, dRect.z / w, dRect.w / h);
StretchRectAuto(ds, sRect, m_ds_as_rt, dRect, Nearest);
}
}
void GSDevice::EndDSAsRT()
+1 -1
View File
@@ -899,7 +899,7 @@ struct alignas(16) GSHWDrawConfig
__fi bool HasDepthROV() const
{
return rov_depth == PS_ROV_DEPTH::READ_ONLY || rov_depth == PS_ROV_DEPTH::READ_WRITE;
return rov_depth != PS_ROV_DEPTH::NONE;
}
__fi bool HasDepthROVWrite() const
+1 -1
View File
@@ -605,6 +605,6 @@ bool D3D12ShaderCache::AddPipelineToBlob(const CacheIndexKey& key, ID3D12Pipelin
return false;
}
m_shader_index.emplace(key, data);
m_pipeline_index.emplace(key, data);
return true;
}
+17 -19
View File
@@ -3626,7 +3626,7 @@ void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, Res
}
else
{
handle = m_null_texture->GetSRVDescriptor();
handle = GetResourceDescriptor(m_null_texture.get(), type);
}
if (m_tfx_textures[i] == handle)
@@ -3675,7 +3675,7 @@ void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool wri
else
{
// Unbind to avoid conflicts with OM targets.
PSSetShaderResource(TEXTURE_RT_UAV, nullptr, false);
PSSetShaderResource(TEXTURE_RT_UAV, nullptr, false, ResourceType::UAV);
}
if (d12Ds)
@@ -3699,7 +3699,7 @@ void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool wri
else
{
// Unbind to avoid conflicts with OM targets.
PSSetShaderResource(TEXTURE_DEPTH_UAV, nullptr, false);
PSSetShaderResource(TEXTURE_DEPTH_UAV, nullptr, false, ResourceType::UAV);
}
}
@@ -3777,7 +3777,8 @@ void GSDevice12::UnbindTexture(GSTexture12* tex)
// RT / primid / depth
for (u32 i = TEXTURE_RT; i <= TEXTURE_DEPTH; i++)
{
if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor())
if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor() ||
m_tfx_textures[i] == tex->GetUAVDescriptor())
{
m_tfx_textures[i] = m_null_texture->GetSRVDescriptor();
m_dirty_flags |= DIRTY_FLAG_TFX_RT_TEXTURES;
@@ -4529,22 +4530,19 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
(draw_ds && static_cast<GSTexture12*>(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0])))
PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false);
if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
// Avoid restarting the render pass just to switch from rt+depth to rt and vice versa.
// Keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth.
if (InRenderPass() && !(draw_rt || draw_rt_rov) && draw_ds && m_current_render_target && config.tex != m_current_render_target &&
m_current_render_target->GetSize() == draw_ds->GetSize())
{
// avoid restarting the render pass just to switch from rt+depth to rt and vice versa
// keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth
if (!(draw_rt || draw_rt_rov) && m_current_render_target && config.tex != m_current_render_target &&
draw_ds && m_current_render_target->GetSize() == draw_ds->GetSize())
{
draw_rt = m_current_render_target;
m_pipeline_selector.rt = true;
}
else if (!(draw_ds || draw_ds_rov) && m_current_depth_target && config.tex != m_current_depth_target &&
draw_rt && m_current_depth_target->GetSize() == draw_rt->GetSize())
{
draw_ds = m_current_depth_target;
m_pipeline_selector.ds = true;
}
draw_rt = m_current_render_target;
m_pipeline_selector.rt = true;
}
else if (InRenderPass() && !(draw_ds || draw_ds_rov) && draw_rt && m_current_depth_target && config.tex != m_current_depth_target &&
m_current_depth_target->GetSize() == draw_rt->GetSize())
{
draw_ds = m_current_depth_target;
m_pipeline_selector.ds = true;
}
GSTexture12* draw_ds_as_rt = static_cast<GSTexture12*>(m_ds_as_rt);
+75 -67
View File
@@ -5331,31 +5331,36 @@ bool GSRendererHW::VerifyIndices()
return true;
}
// Fix the colors in vertices in case the API only supports "provoking first vertex"
// (i.e., when using flat shading the color comes from the first vertex, unlike PS2
// which is "provoking last vertex").
void GSRendererHW::HandleProvokingVertexFirst()
void GSRendererHW::HandleFlatShadedVertices()
{
// Early exit conditions:
if (g_gs_device->Features().provoking_vertex_last || // device supports provoking last vertex
m_conf.vs.iip || // we are doing Gouraud shading
m_vt.m_primclass == GS_POINT_CLASS || // drawing points (one vertex per primitive; color is unambiguous)
m_vt.m_primclass == GS_SPRITE_CLASS) // drawing sprites (handled by the sprites -> triangles expand shader)
// These cases might need fixing.
const bool maybe_fix_vertices = !m_conf.vs.iip &&
(!g_gs_device->Features().provoking_vertex_last || IsCoverageAlphaSupported());
// These cases definitely don't need fixing.
const bool dont_fix_vertices = m_vt.m_primclass == GS_POINT_CLASS || m_vt.m_primclass == GS_SPRITE_CLASS;
if (!maybe_fix_vertices || dont_fix_vertices)
return;
const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
// If all first/last vertices have the same color there is nothing to do.
bool first_eq_last = true;
// If all vertices of each prim have the same color there is nothing to do.
bool prims_flat = true;
for (u32 i = 0; i < m_index->tail; i += n)
{
if (m_vertex->buff[m_index->buff[i]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
for (u32 j = 0; j < n - 1; j++)
{
first_eq_last = false;
break;
if (m_vertex->buff[m_index->buff[i + j]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
{
prims_flat = false;
break;
}
}
if (!prims_flat)
break;
}
if (first_eq_last)
if (prims_flat)
return;
// De-index the vertices using the copy buffer
@@ -5369,11 +5374,11 @@ void GSRendererHW::HandleProvokingVertexFirst()
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
// Put correct color in the first vertex
// Make all vertices the same color to simplify handling in expand shaders.
for (u32 i = 0; i < m_index->tail; i += n)
{
m_vertex->buff[i].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
m_vertex->buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
for (u32 j = 0; j < n - 1; j++)
m_vertex->buff[i + j].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
}
}
@@ -7533,7 +7538,11 @@ __fi void GSRendererHW::GetForcedROVUsage(bool& rov_color, bool& rov_depth)
// Separate flag for DATE since they are many methods and the interaction with ROV is not clear.
const bool date = m_cached_ctx.TEST.DATE;
if (rov_color && (m_conf.ps.HasShaderDiscard() || m_conf.ps.HasDepthOutput() || date))
// Alpha test might require depth feedback once we configure ROV.
const bool atst_needs_depth = m_cached_ctx.TEST.ATE &&
(m_cached_ctx.TEST.AFAIL == AFAIL_FB_ONLY || m_cached_ctx.TEST.AFAIL == AFAIL_RGB_ONLY);
if (rov_color && (m_conf.ps.HasShaderDiscard() || m_conf.ps.HasDepthOutput() || date || atst_needs_depth))
{
GL_INS("ROV: Color ROV with shader discard/depth write forces depth ROV");
rov_depth = true;
@@ -7798,60 +7807,61 @@ void GSRendererHW::ConvertTextureTypeROVSingle(GSTextureCache::Target* tgt, bool
GSTexture* old_tex = depth ? m_conf.ds : m_conf.rt;
GSTexture::Usage usage = shader_write ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
GSTexture* new_tex = depth ?
const GSTexture::Usage usage = shader_write ? GSTexture::ShaderWriteTarget : GSTexture::FeedbackTarget;
if (GSTexture* new_tex = depth ?
(shader_write ?
g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::DepthColor, false, true) :
g_gs_device->CreateDepthStencil(old_tex->GetSize(), false, true)) :
g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::Color, false, true);
switch (old_tex->GetState())
g_gs_device->FetchSurface(usage, old_tex->GetSize(), 1, GSTexture::Format::Color, false, true))
{
case GSTexture::State::Cleared:
if (depth)
g_gs_device->ClearDepth(new_tex, old_tex->GetClearDepth());
else
g_gs_device->ClearRenderTarget(new_tex, old_tex->GetClearColor());
break;
case GSTexture::State::Invalidated:
g_gs_device->InvalidateRenderTarget(new_tex);
break;
case GSTexture::State::Dirty:
g_gs_device->StretchRectAuto(old_tex, new_tex, Nearest);
switch (old_tex->GetState())
{
case GSTexture::State::Cleared:
if (depth)
g_gs_device->ClearDepth(new_tex, old_tex->GetClearDepth());
else
g_gs_device->ClearRenderTarget(new_tex, old_tex->GetClearColor());
break;
case GSTexture::State::Invalidated:
g_gs_device->InvalidateRenderTarget(new_tex);
break;
case GSTexture::State::Dirty:
g_gs_device->StretchRectAuto(old_tex, new_tex, Nearest);
// Count stats as part of both standard and ROV.
g_perfmon.Put(GSPerfMon::TextureCopiesROV, 1.0);
g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0);
break;
default:
pxAssert(false);
break;
}
// Count stats as part of both standard and ROV.
g_perfmon.Put(GSPerfMon::TextureCopiesROV, 1.0);
g_perfmon.Put(GSPerfMon::DrawCallsROV, 1.0);
break;
default:
pxAssert(false);
break;
}
#if PCSX2_DEVBUILD
new_tex->SetDebugName(tgt->m_texture->GetDebugName());
new_tex->SetDebugName(tgt->m_texture->GetDebugName());
#endif
if (tgt->m_texture == old_tex)
{
GL_CACHE("HW: Replaced texture for %s @ 0x%04x", depth ? "DS" : "RT", tgt->m_TEX0.TBP0);
tgt->m_texture = new_tex;
}
else
{
// Must be the temporary Z.
pxAssert(depth && g_texture_cache->GetTemporaryZ() == old_tex);
GL_CACHE("HW: Replaced texture for temporary Z @ 0x%04x", g_texture_cache->GetTemporaryZInfo().ZBP);
g_texture_cache->SetTemporaryZ(new_tex);
}
if (tgt->m_texture == old_tex)
{
GL_CACHE("HW: Replaced texture for %s @ 0x%04x", depth ? "DS" : "RT", tgt->m_TEX0.TBP0);
tgt->m_texture = new_tex;
}
else
{
// Must be the temporary Z.
pxAssert(depth && g_texture_cache->GetTemporaryZ() == old_tex);
GL_CACHE("HW: Replaced texture for temporary Z @ 0x%04x", g_texture_cache->GetTemporaryZInfo().ZBP);
g_texture_cache->SetTemporaryZ(new_tex);
}
// Fixup the backend config.
if (depth)
m_conf.ds = new_tex;
else
m_conf.rt = new_tex;
// Fixup the backend config.
if (depth)
m_conf.ds = new_tex;
else
m_conf.rt = new_tex;
g_gs_device->Recycle(old_tex);
g_gs_device->Recycle(old_tex);
}
}
void GSRendererHW::ConvertTextureTypeROV(GSTextureCache::Target* rt, GSTextureCache::Target* ds)
@@ -9445,7 +9455,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
DetermineROVUsage(rt, ds);
ConvertTextureTypeROV(rt, ds);
// Barriers must be determined before indices are modified via HandleProvokingVertexFirst/SetupIA.
// Barriers must be determined before indices are modified via HandleFlatShadedVertices/SetupIA.
// This also computes the drawlist if needed.
DetermineBarriers(rt, tex);
@@ -9470,7 +9480,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.scissor = (date_options.enabled && !date_options.barrier) ? m_conf.drawarea : scissor;
HandleProvokingVertexFirst();
HandleFlatShadedVertices();
SetupIA(rtscale, vs_scale_x, vs_scale_y, m_channel_shuffle_width != 0, no_rt);
@@ -10500,9 +10510,7 @@ bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Sourc
r_texture.w -= offset;
const int new_height = std::max(r_texture.w, th);
GSTexture* temp_tex = g_gs_device->CreateTexture(tw, new_height, 1, tex->m_texture->GetFormat(), true);
if (temp_tex)
if (GSTexture* temp_tex = g_gs_device->CreateTexture(tw, new_height, 1, tex->m_texture->GetFormat(), true))
{
if (GSTexture* rt = g_gs_device->CreateFeedbackTarget(tw, new_height, GSTexture::Format::Color))
{
+1 -1
View File
@@ -215,7 +215,7 @@ private:
void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm);
void ResetStates();
void HandleProvokingVertexFirst();
void HandleFlatShadedVertices();
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup, const bool no_rt);
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex);
u32 EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);
+2 -1
View File
@@ -5296,7 +5296,8 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
req_resize = true;
// If it was matched to an old target, make sure to clear the other type and update its information.
if (dst->m_was_dst_matched)
// Also update information if the buffer width of the new data is larger than the existing target.
if (dst->m_was_dst_matched || static_cast<u32>(Common::AlignUpPow2(w, 64) / GSLocalMemory::m_psm[new_TEX0.PSM].pgs.x) > dst->m_TEX0.TBW)
{
dst->m_TEX0 = new_TEX0;
}
+11
View File
@@ -279,6 +279,7 @@ public:
std::unordered_map<PipelineSelectorMTL, MRCOwned<id<MTLRenderPipelineState>>> m_hw_pipeline;
MRCOwned<MTLRenderPassDescriptor*> m_render_pass_desc[16];
MRCOwned<MTLRenderPassDescriptor*> m_full_rov_render_pass_desc;
MRCOwned<id<MTLSamplerState>> m_sampler_hw[1 << 8];
@@ -316,12 +317,14 @@ public:
// Clear line (Things below here are tracked by `has` and don't need to be cleared to reset)
SamplerSelector sampler_sel;
u8 blend_color;
struct { u32 w, h; } full_rov_size;
GSVector4i scissor;
PipelineSelectorMTL pipeline_sel;
GSHWDrawConfig::VSConstantBuffer cb_vs;
GSHWDrawConfig::PSConstantBuffer cb_ps;
MainRenderEncoder(const MainRenderEncoder&) = delete;
MainRenderEncoder() = default;
bool is_full_rov() const { return encoder && !color_target && !depth_target && !stencil_target; }
} m_current_render;
MRCOwned<id<MTLCommandBuffer>> m_texture_upload_cmdbuf;
MRCOwned<id<MTLBlitCommandEncoder>> m_texture_upload_encoder;
@@ -330,6 +333,8 @@ public:
MRCOwned<id<MTLBlitCommandEncoder>> m_vertex_upload_encoder;
id<MTLTexture> m_ds_as_rt_texture = nil;
GSTexture* m_ds_as_rt_gstexture = nullptr;
MRCOwned<id<MTLTexture>> m_rov_dummy_texture;
struct { u32 w, h; } m_rov_dummy_texture_size = {};
struct DebugEntry
{
@@ -372,8 +377,12 @@ public:
void FlushEncodersForReadback();
/// End current render pass without flushing
void EndRenderPass();
/// Prepare to begin a new render pass
void PrepareBeginRenderPass();
/// Begin a new render pass (may reuse existing)
void BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil = nullptr, MTLLoadAction stencil_load = MTLLoadActionDontCare, bool rt1 = false);
/// Begin a new full-ROV render pass (may reuse existing)
void BeginFullROV(NSString* name, uint32_t width, uint32_t height);
/// Call at the end of each frame
void FrameCompleted();
@@ -442,6 +451,7 @@ public:
void MRESetDSS(id<MTLDepthStencilState> dss);
void MRESetSampler(SamplerSelector sel);
void MRESetTexture(GSTexture* tex, int pos);
void MRESetTexture(id<MTLTexture> tex, int pos);
void MRESetVertices(id<MTLBuffer> buffer, size_t offset);
void MRESetVSIndices(id<MTLBuffer> buffer, size_t offset);
void MRESetScissor(const GSVector4i& scissor);
@@ -455,6 +465,7 @@ public:
// MARK: Render HW
void SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, SetDATM datm);
void PrepareROVTexture(GSTexture** ptex);
void RenderHW(GSHWDrawConfig& config) override;
void SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder> enc, id<MTLBuffer> buffer, size_t off,
bool one_barrier, bool full_barrier);
+161 -29
View File
@@ -419,6 +419,19 @@ static GSVector4 GetRTLoadInfo(GSTextureMTL* tex, MTLLoadAction* load_action)
return {};
};
void GSDeviceMTL::PrepareBeginRenderPass()
{
m_encoders_in_current_cmdbuf++;
if (m_late_texture_upload_encoder)
{
[m_late_texture_upload_encoder endEncoding];
m_late_texture_upload_encoder = nullptr;
}
EndRenderPass();
}
void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load,
GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil, MTLLoadAction stencil_load, bool rt1)
{
@@ -455,14 +468,6 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
return;
}
m_encoders_in_current_cmdbuf++;
if (m_late_texture_upload_encoder)
{
[m_late_texture_upload_encoder endEncoding];
m_late_texture_upload_encoder = nullptr;
}
int idx = 0;
if (mc) idx |= 1;
if (md) idx |= 2;
@@ -502,7 +507,7 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
color1.clearColor = MTLClearColorMake(depth_load == MTLLoadActionClear ? depth_clear.x : -1, 0, 0, 0);
}
EndRenderPass();
PrepareBeginRenderPass();
m_current_render.encoder = MRCRetain([GetRenderCmdBuf() renderCommandEncoderWithDescriptor:desc]);
m_current_render.name = (__bridge void*)name;
[m_current_render.encoder setLabel:name];
@@ -516,6 +521,61 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
pxAssertRel(m_current_render.encoder, "Failed to create render encoder!");
}
void GSDeviceMTL::BeginFullROV(NSString* name, uint32_t width, uint32_t height)
{
bool needs_new = !m_current_render.is_full_rov()
|| width > m_current_render.full_rov_size.w
|| height > m_current_render.full_rov_size.h;
if (!needs_new)
{
if (m_current_render.name != (__bridge void*)name)
{
m_current_render.name = (__bridge void*)name;
[m_current_render.encoder setLabel:name];
}
return;
}
if (m_current_render.is_full_rov())
{
// Render was too small. Don't let it shrink in one dimension while growing in the other.
width = std::max(width, m_current_render.full_rov_size.w);
height = std::max(height, m_current_render.full_rov_size.h);
}
m_current_render.full_rov_size.w = width;
m_current_render.full_rov_size.h = height;
MTLRenderPassDescriptor* desc = m_full_rov_render_pass_desc;
[desc setRenderTargetWidth:width];
[desc setRenderTargetHeight:height];
if (m_dev.features.rov_requires_rt && (width > m_rov_dummy_texture_size.w || height > m_rov_dummy_texture_size.h))
{
m_rov_dummy_texture_size.w = std::max(m_rov_dummy_texture_size.w, width);
m_rov_dummy_texture_size.h = std::max(m_rov_dummy_texture_size.h, height);
MTLTextureDescriptor* tdesc = [MTLTextureDescriptor
texture2DDescriptorWithPixelFormat:MTLPixelFormatR8Unorm
width:m_rov_dummy_texture_size.w
height:m_rov_dummy_texture_size.h
mipmapped:NO];
[tdesc setUsage:MTLTextureUsageRenderTarget];
[tdesc setStorageMode:MTLStorageModePrivate];
id<MTLTexture> tex = m_rov_dummy_texture = MRCTransfer([m_dev.dev newTextureWithDescriptor:tdesc]);
[tex setLabel:@"ROV Dummy Texture"];
[[[desc colorAttachments] objectAtIndexedSubscript:0] setTexture:tex];
}
PrepareBeginRenderPass();
m_current_render.encoder = MRCRetain([GetRenderCmdBuf() renderCommandEncoderWithDescriptor:desc]);
m_current_render.name = (__bridge void*)name;
[m_current_render.encoder setLabel:name];
if (!m_dev.features.unified_memory)
[m_current_render.encoder waitForFence:m_draw_sync_fence
beforeStages:MTLRenderStageVertex];
}
void GSDeviceMTL::FrameCompleted()
{
if (m_spin_timer)
@@ -564,11 +624,10 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Usage usage, int width, int hei
[desc setStorageMode:MTLStorageModePrivate];
MTLTextureUsage mtl_usage = MTLTextureUsageShaderRead;
bool needs_rov_tex = false;
if (GSTexture::IsRenderTarget(usage))
{
if (GSTexture::IsRenderTargetOrDepthStencil(usage))
mtl_usage |= MTLTextureUsageRenderTarget;
}
if ((usage & GSTexture::FeedbackTarget) == GSTexture::FeedbackTarget)
{
@@ -576,17 +635,23 @@ GSTexture* GSDeviceMTL::CreateSurface(GSTexture::Usage usage, int width, int hei
mtl_usage |= MTLTextureUsagePixelFormatView; // Force color compression off by including PixelFormatView
}
if (GSTexture::IsShaderWrite(usage))
if (usage == GSTexture::ShaderWriteTarget && format == GSTexture::Format::Color && m_dev.features.rov_requires_r32)
{
mtl_usage |= MTLTextureUsageShaderWrite;
// Need to make an R32 view of the texture for ROV
mtl_usage |= MTLTextureUsagePixelFormatView;
needs_rov_tex = true;
}
if (GSTexture::IsShaderWrite(usage))
mtl_usage |= MTLTextureUsageShaderWrite;
[desc setUsage:mtl_usage];
MRCOwned<id<MTLTexture>> tex = MRCTransfer([m_dev.dev newTextureWithDescriptor:desc]);
if (tex)
{
GSTextureMTL* t = new GSTextureMTL(this, tex, usage, format);
MRCOwned<id<MTLTexture>> rov_tex = needs_rov_tex ? MRCTransfer([tex newTextureViewWithPixelFormat:MTLPixelFormatR32Uint]) : nil;
GSTextureMTL* t = new GSTextureMTL(this, tex, rov_tex, usage, format);
if (GSTexture::IsRenderTarget(usage))
{
ClearRenderTarget(t, 0);
@@ -1003,12 +1068,14 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_features.test_and_sample_depth = true;
m_features.depth_feedback = getDepthFeedback(m_dev, m_features.framebuffer_fetch);
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand;
m_features.rov = m_dev.features.rov && !m_features.framebuffer_fetch;
m_max_texture_size = m_dev.features.max_texsize;
// Init metal stuff
m_fn_constants = MRCTransfer([MTLFunctionConstantValues new]);
setFnConstantB(m_fn_constants, m_features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH);
setFnConstantB(m_fn_constants, m_features.depth_feedback, GSMTLConstantIndex_DEPTH_FEEDBACK);
setFnConstantB(m_fn_constants, m_features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH);
setFnConstantB(m_fn_constants, m_features.depth_feedback, GSMTLConstantIndex_DEPTH_FEEDBACK);
setFnConstantB(m_fn_constants, m_dev.features.rov_requires_r32, GSMTLConstantIndex_ROV_NEEDS_R32);
m_draw_sync_fence = MRCTransfer([m_dev.dev newFence]);
[m_draw_sync_fence setLabel:@"Draw Sync Fence"];
@@ -1066,6 +1133,17 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
}
m_render_pass_desc[i] = desc;
}
if (m_features.rov)
{
m_full_rov_render_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]);
[m_full_rov_render_pass_desc setDefaultRasterSampleCount:1];
if (m_dev.features.rov_requires_rt)
{
MTLRenderPassColorAttachmentDescriptor* color0 = [[m_full_rov_render_pass_desc colorAttachments] objectAtIndexedSubscript:0];
[color0 setLoadAction:MTLLoadActionDontCare];
[color0 setStoreAction:MTLStoreActionDontCare];
}
}
// Init samplers
m_sampler_hw[SamplerSelector::Linear().key] = CreateSampler(m_dev.dev, SamplerSelector::Linear());
@@ -1299,6 +1377,19 @@ bool GSDeviceMTL::UpdateWindow()
bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; }
static const char* GetROVString(const GSMTLDevice::Features& features)
{
if (!features.rov)
return "Unsupported";
if (features.rov_requires_r32 && features.rov_requires_rt)
return "With R32 Reinterpret and RT Texture";
if (features.rov_requires_r32)
return "With R32 Reinterpret";
if (features.rov_requires_rt)
return "With RT Texture";
return "Supported";
}
std::string GSDeviceMTL::GetDriverInfo() const
{ @autoreleasepool {
std::string desc([[m_dev.dev description] UTF8String]);
@@ -1308,6 +1399,7 @@ std::string GSDeviceMTL::GetDriverInfo() const
desc += "\n Memoryless Textures: " + std::string(m_dev.features.memoryless_textures ? "Supported" : "Unsupported");
desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported");
desc += "\n Depth Feedback: " + std::string(m_dev.features.depth_feedback ? "Supported" : "Unsupported");
desc += "\n ROV: " + std::string(GetROVString(m_dev.features));
desc += "\n Shader Version: " + std::string(to_string(m_dev.features.shader_version));
desc += "\n Max Texture Size: " + std::to_string(m_dev.features.max_texsize);
return desc;
@@ -1968,7 +2060,10 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
setFnConstantI(m_fn_constants, pssel.aa1, GSMTLConstantIndex_PS_AA1);
setFnConstantB(m_fn_constants, pssel.abe, GSMTLConstantIndex_PS_ABE);
setFnConstantI(m_fn_constants, pssel.sw_aniso, GSMTLConstantIndex_PS_SW_ANISO);
auto newps = LoadShader(@"ps_main");
setFnConstantB(m_fn_constants, pssel.rov_color, GSMTLConstantIndex_PS_ROV_COLOR);
setFnConstantI(m_fn_constants, pssel.rov_depth, GSMTLConstantIndex_PS_ROV_DEPTH);
bool eft = pssel.HasColorROV() && !pssel.HasDepthROV() && !pssel.HasDepthOutput();
auto newps = LoadShader(eft ? @"ps_main_rov_eft" : @"ps_main");
ps = newps;
m_hw_ps.insert(std::make_pair(pssel, std::move(newps)));
}
@@ -2007,6 +2102,12 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
MTLRenderPipelineColorAttachmentDescriptor* color1 = [[pdesc colorAttachments] objectAtIndexedSubscript:1];
[color1 setPixelFormat:MTLPixelFormatR32Float];
}
if (m_dev.features.rov_requires_rt && extras.rt == GSTexture::Format::Invalid && !extras.has_depth && !extras.has_stencil)
{
// Dummy texture
[color setPixelFormat:MTLPixelFormatR8Unorm];
[color setWriteMask:MTLColorWriteMaskNone];
}
NSString* pname = [NSString stringWithFormat:@"HW Render %x.%llx.%llx.%x", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey];
auto pipeline = MakePipeline(pdesc, vs, ps, pname);
@@ -2048,14 +2149,21 @@ static void textureBarrier(id<MTLRenderCommandEncoder> enc)
void GSDeviceMTL::MRESetTexture(GSTexture* tex, int pos)
{
if (tex == m_current_render.tex[pos])
if (!tex || tex == m_current_render.tex[pos])
return;
m_current_render.tex[pos] = tex;
if (GSTextureMTL* mtex = static_cast<GSTextureMTL*>(tex))
{
[m_current_render.encoder setFragmentTexture:mtex->GetTexture() atIndex:pos];
mtex->m_last_read = m_current_draw;
}
GSTextureMTL* mtex = static_cast<GSTextureMTL*>(tex);
[m_current_render.encoder setFragmentTexture:mtex->GetTexture() atIndex:pos];
mtex->m_last_read = m_current_draw;
}
void GSDeviceMTL::MRESetTexture(id<MTLTexture> tex, int pos)
{
GSTexture* gstex = reinterpret_cast<GSTexture*>(tex); // No one actually dereferences this
if (!tex || gstex == m_current_render.tex[pos])
return;
m_current_render.tex[pos] = gstex;
[m_current_render.encoder setFragmentTexture:tex atIndex:pos];
}
void GSDeviceMTL::MRESetVertices(id<MTLBuffer> buffer, size_t offset)
@@ -2100,6 +2208,7 @@ void GSDeviceMTL::MRESetScissor(const GSVector4i& scissor)
void GSDeviceMTL::MREClearScissor()
{
assert(!m_current_render.is_full_rov());
if (!m_current_render.has.scissor)
return;
m_current_render.has.scissor = false;
@@ -2227,6 +2336,15 @@ void GSDeviceMTL::MREInitHWDraw(GSHWDrawConfig& config, const Map& verts)
MRESetVSIndices(verts.gpu_buffer, verts.gpu_offset + config.nverts * sizeof(*config.verts));
}
__fi void GSDeviceMTL::PrepareROVTexture(GSTexture** ptex)
{
GSTextureMTL* tex = static_cast<GSTextureMTL*>(*ptex);
FlushClears(tex);
tex->m_last_read = m_current_draw;
tex->m_last_write = m_current_draw;
*ptex = nullptr;
}
void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
{ @autoreleasepool {
if (config.tex && (config.ds == config.tex || config.rt == config.tex))
@@ -2363,7 +2481,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
}
// Try to reduce render pass restarts
if (!config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex)
if (!config.ps.HasColorROV() && !config.ds && m_current_render.color_target == rt && stencil == m_current_render.stencil_target && m_current_render.depth_target != config.tex)
config.ds = m_current_render.depth_target;
if (!rt && config.ds == m_current_render.depth_target && m_current_render.color_target != config.tex)
rt = m_current_render.color_target;
@@ -2378,16 +2496,30 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
}
const bool feedback_depth = config.ps.IsFeedbackLoopDepth();
const bool rt1 = feedback_depth && !m_features.depth_feedback;
BeginRenderPass(@"RenderHW", rt, MTLLoadActionLoad, config.ds, MTLLoadActionLoad, stencil, MTLLoadActionLoad, rt1);
const bool rt1 = feedback_depth && !m_features.depth_feedback && !config.ps.HasDepthROV();
GSTexture* rt_bind = rt;
GSTexture* ds_bind = config.ds;
GSTexture* rt_size = rt_bind ? rt_bind : ds_bind;
if (config.ps.HasColorROV() && rt_bind) PrepareROVTexture(&rt_bind);
if (config.ps.HasDepthROV() && ds_bind) PrepareROVTexture(&ds_bind);
if (!rt_bind && !ds_bind && !stencil)
BeginFullROV(@"RenderHWROV", rt_size->GetWidth(), rt_size->GetHeight());
else
BeginRenderPass(@"RenderHW", rt_bind, MTLLoadActionLoad, ds_bind, MTLLoadActionLoad, stencil, MTLLoadActionLoad, rt1);
id<MTLRenderCommandEncoder> mtlenc = m_current_render.encoder;
FlushDebugEntries(mtlenc);
if (usesStencil(config.destination_alpha))
[mtlenc setStencilReferenceValue:1];
MREInitHWDraw(config, allocation);
if (config.require_one_barrier || config.require_full_barrier)
if (config.ps.HasColorROV() && m_dev.features.rov_requires_r32)
MRESetTexture(reinterpret_cast<GSTextureMTL*>(rt)->GetROVTexture(), GSMTLTextureIndexRenderTarget);
else if (config.require_one_barrier || config.require_full_barrier || config.ps.HasColorROV())
MRESetTexture(rt, GSMTLTextureIndexRenderTarget);
if (feedback_depth)
if (config.ps.HasDepthROV())
{
MRESetTexture(config.ds, GSMTLTextureIndexDepthTarget);
}
else if (feedback_depth)
{
GSTexture* tex = !m_features.depth_feedback && !m_features.framebuffer_fetch ? m_ds_as_rt : config.ds;
MRESetTexture(tex, GSMTLTextureIndexDepthTarget);
@@ -34,6 +34,9 @@ struct GSMTLDevice
bool has_fast_half : 1;
bool memoryless_textures : 1;
bool depth_feedback : 1;
bool rov : 1;
bool rov_requires_rt : 1;
bool rov_requires_r32 : 1;
MetalVersion shader_version;
int max_texsize;
};
@@ -166,6 +166,9 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
features.depth_feedback = false;
features.rov = [dev areRasterOrderGroupsSupported];
features.rov_requires_r32 = [dev readWriteTextureSupport] < MTLReadWriteTextureTier2;
NSString* name = [dev name];
if ([name containsString:@"Intel"])
{
@@ -193,6 +196,9 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
if (@available(macOS 13, iOS 16, *))
if ([dev supportsFamily:MTLGPUFamilyMetal3])
features.depth_feedback = true;
// ROV doesn't work on AMD GPUs with no render target
// Ideally the driver would insert a dummy texture for us (it does on Linux) but the Metal driver doesn't.
features.rov_requires_rt = true;
}
else if ([name containsString:@"NVIDIA"])
{
@@ -219,6 +225,9 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
else
features.slow_color_compression = [[dev name] containsString:@"AMD"] || [[dev name] isEqualToString:@"Intel HD Graphics 4000"];
if (char* env = getenv("MTL_ROV_WITH_RT"))
features.rov_requires_rt = env[0] == '1' || env[0] == 'y' || env[0] == 'Y';
features.max_texsize = GetMaxTextureSize(dev);
this->dev = std::move(dev);
@@ -165,6 +165,7 @@ enum GSMTLFnConstants
GSMTLConstantIndex_CAS_SHARPEN_ONLY,
GSMTLConstantIndex_FRAMEBUFFER_FETCH,
GSMTLConstantIndex_DEPTH_FEEDBACK,
GSMTLConstantIndex_ROV_NEEDS_R32,
GSMTLConstantIndex_FST,
GSMTLConstantIndex_IIP,
GSMTLConstantIndex_VS_POINT_SIZE,
@@ -227,4 +228,6 @@ enum GSMTLFnConstants
GSMTLConstantIndex_PS_AA1,
GSMTLConstantIndex_PS_ABE,
GSMTLConstantIndex_PS_SW_ANISO,
GSMTLConstantIndex_PS_ROV_COLOR,
GSMTLConstantIndex_PS_ROV_DEPTH,
};
+3 -1
View File
@@ -21,12 +21,13 @@ class GSTextureMTL : public GSTexture
{
GSDeviceMTL* m_dev;
MRCOwned<id<MTLTexture>> m_texture;
MRCOwned<id<MTLTexture>> m_rov_texture;
bool m_has_mipmaps = false;
public:
u64 m_last_read = 0; ///< Last time this texture was read by a draw
u64 m_last_write = 0; ///< Last time this texture was written by a draw
GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, Usage usage, Format format);
GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, MRCOwned<id<MTLTexture>> rov_texture, Usage usage, Format format);
~GSTextureMTL();
/// For making fake backbuffers
@@ -42,6 +43,7 @@ public:
void Unmap() override;
void GenerateMipmap() override;
id<MTLTexture> GetTexture() { return m_texture; }
id<MTLTexture> GetROVTexture() { return m_rov_texture; }
#ifdef PCSX2_DEVBUILD
void SetDebugName(std::string_view name) override;
+4 -1
View File
@@ -13,9 +13,10 @@
// Uploads/downloads need 32-byte alignment for AVX2.
static constexpr u32 PITCH_ALIGNMENT = 32;
GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, Usage usage, Format format)
GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, MRCOwned<id<MTLTexture>> rov_texture, Usage usage, Format format)
: m_dev(dev)
, m_texture(std::move(texture))
, m_rov_texture(std::move(rov_texture))
{
m_usage = usage;
m_format = format;
@@ -141,6 +142,8 @@ void GSTextureMTL::SetDebugName(std::string_view name)
length:static_cast<NSUInteger>(name.length())
encoding:NSUTF8StringEncoding];
[m_texture setLabel:label];
if (m_rov_texture)
[m_rov_texture setLabel:[label stringByAppendingString:@".ROV"]];
}
m_debug_name = std::move(name);
+187 -85
View File
@@ -13,6 +13,7 @@ constant uint SHUFFLE_READWRITE = 3;
constant bool HAS_FBFETCH [[function_constant(GSMTLConstantIndex_FRAMEBUFFER_FETCH)]];
constant bool DEPTH_FEEDBACK [[function_constant(GSMTLConstantIndex_DEPTH_FEEDBACK)]];
constant bool ROV_NEEDS_R32 [[function_constant(GSMTLConstantIndex_ROV_NEEDS_R32)]];
constant bool FST [[function_constant(GSMTLConstantIndex_FST)]];
constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]];
constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]];
@@ -75,17 +76,21 @@ constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SC
constant uint PS_AA1_RAW [[function_constant(GSMTLConstantIndex_PS_AA1)]];
constant bool PS_ABE [[function_constant(GSMTLConstantIndex_PS_ABE)]];
constant uint PS_SW_ANISO [[function_constant(GSMTLConstantIndex_PS_SW_ANISO)]];
constant bool PS_ROV_COLOR [[function_constant(GSMTLConstantIndex_PS_ROV_COLOR)]];
constant uint PS_ROV_DEPTH_RAW [[function_constant(GSMTLConstantIndex_PS_ROV_DEPTH)]];
using GSShader::VSExpand;
using AFAIL = GSShader::PS_AFAIL;
using ATST = GSShader::PS_ATST;
using GSShader::ZTST;
using AA1 = GSShader::PS_AA1;
using ROV_DEPTH = GSShader::PS_ROV_DEPTH;
constant VSExpand VS_EXPAND_TYPE = static_cast<VSExpand>(VS_EXPAND_TYPE_RAW);
constant AFAIL PS_AFAIL = static_cast<AFAIL>(PS_AFAIL_RAW);
constant ATST PS_ATST = static_cast<ATST>(PS_ATST_RAW);
constant ZTST PS_ZTST = static_cast<ZTST>(PS_ZTST_RAW);
constant AA1 PS_AA1 = static_cast<AA1>(PS_AA1_RAW);
constant ROV_DEPTH PS_ROV_DEPTH = static_cast<ROV_DEPTH>(PS_ROV_DEPTH_RAW);
#if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220
#define PRIMID_SUPPORT 1
@@ -119,9 +124,9 @@ constant bool NEEDS_DEPTH_FOR_ZTST = PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST
constant bool NEEDS_DEPTH_FOR_AA1 = PS_AA1 == AA1::TRIANGLE_SW_Z;
constant bool SW_DEPTH = NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1;
constant bool PS_COLOR0 = !PS_NO_COLOR;
constant bool PS_COLOR1 = !PS_NO_COLOR1;
constant bool PS_ZOUTPUT = PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH;
constant bool PS_OUTPUT_COLOR0 = !PS_NO_COLOR && !PS_ROV_COLOR;
constant bool PS_OUTPUT_COLOR1 = !PS_NO_COLOR1 && !PS_ROV_COLOR;
constant bool PS_ZOUTPUT = (PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
constant bool PS_ZOUTPUT_LESS = PS_ZOUTPUT && !SW_DEPTH;
constant bool PS_ZOUTPUT_ANY = PS_ZOUTPUT && SW_DEPTH;
constant bool PS_ZOUTPUT_COLOR = PS_ZOUTPUT_ANY && !DEPTH_FEEDBACK;
@@ -165,21 +170,32 @@ struct MainPSIn
uint interior [[function_constant(PS_INTERIOR)]];
};
struct MainResult
{
float4 c0;
float4 c1;
float depth;
};
struct MainPSOut
{
float4 c0 [[color(0), index(0), function_constant(PS_COLOR0)]];
float4 c1 [[color(0), index(1), function_constant(PS_COLOR1)]];
float4 c0 [[color(0), index(0), function_constant(PS_OUTPUT_COLOR0)]];
float4 c1 [[color(0), index(1), function_constant(PS_OUTPUT_COLOR1)]];
float depthColor [[color(1), function_constant(PS_ZOUTPUT_COLOR)]];
float depthLess [[depth(less), function_constant(PS_ZOUTPUT_LESS)]];
float depthAny [[depth(any), function_constant(PS_ZOUTPUT_ANY)]];
void setDepth(float depth)
MainPSOut(MainResult res)
{
if (PS_OUTPUT_COLOR0)
c0 = res.c0;
if (PS_OUTPUT_COLOR1)
c1 = res.c1;
if (PS_ZOUTPUT_LESS)
depthLess = depth;
depthLess = res.depth;
if (PS_ZOUTPUT_ANY)
depthAny = depth;
depthAny = res.depth;
if (PS_ZOUTPUT_COLOR)
depthColor = depth;
depthColor = res.depth;
}
};
@@ -539,12 +555,6 @@ vertex MainVSOut vs_main_expand(
out.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
out.interior = 0;
if (NOT_IIP)
{
// Get the provoking vertex color (first vertex in Metal)
out.fc = i0 == 0 ? out.fc : (i1 == 0 ? other.fc : opposite.fc);
}
}
return out;
@@ -564,11 +574,37 @@ struct PSMain
float4 current_color;
float current_depth;
uint prim_id;
bool color_discarded = false;
bool depth_discarded = false;
const thread MainPSIn& in;
constant GSMTLMainPSUniform& cb;
PSMain(const thread MainPSIn& in, constant GSMTLMainPSUniform& cb): in(in), cb(cb) {}
void discard()
{
if (PS_ROV_COLOR || PS_ROV_DEPTH != ROV_DEPTH::NONE)
color_discarded = depth_discarded = true;
else
discard_fragment();
}
void discard_color(thread float4& output)
{
if (PS_ROV_COLOR)
color_discarded = true;
else
output = current_color;
}
void discard_depth(thread float& output)
{
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE)
depth_discarded = true;
else
output = current_depth;
}
template <typename... Args>
float4 sample_tex(Args... args)
{
@@ -611,22 +647,27 @@ struct PSMain
float4 sample_c_af(float2 uv, float uv_w)
{
// HW sampler will reject bad UVs, match that here.
uv = any(isnan(uv) | isinf(uv)) ? float2(0, 0) : uv;
uv = any(isnan(uv) | isinf(uv)) ? float2(0.0f, 0.0f) : uv;
// Large floating point values risk NaN/Inf values.
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
uv = clamp(uv, -8388608.0f, 8388608.0f);
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
float2 sz = float2(get_tex_dims());
float2 dX = dfdx(uv) * sz;
float2 dY = dfdy(uv) * sz;
float length_x = length(dX);
float length_y = length(dY);
// Calculate Ellipse Transform
bool d_zero = length(dX) == 0 || length(dY) == 0;
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
bool d_per = dot(dX, dY) == 0;
bool d_zero = length_x < 0.001f || length_y < 0.001f;
float f = (dX.x * dY.y - dX.y * dY.x);
bool d_par = f < 0.001f;
bool d_per = dot(dX, dY) < 0.001f;
bool d_inf_nan = any(isinf(dX) | isinf(dY) | isnan(dX) | isnan(dY));
if (!(d_zero || d_par || d_per || d_inf_nan))
@@ -634,21 +675,30 @@ struct PSMain
float A = dX.y * dX.y + dY.y * dY.y;
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
float C = dX.x * dX.x + dY.x * dY.x;
float f = (dX.x * dY.y - dY.x * dX.y);
float F = f * f;
float p = A - C;
float q = A + C;
float t = sqrt(p * p + B * B);
float signB = sign(B);
float denom_plus = t * (q + t);
float denom_minus = t * (q - t);
float sqrtA = sqrt(F * (t + p));
float sqrtB = sqrt(F * (t - p));
float inv_sqrt_denom_plus = rsqrt(denom_plus);
float inv_sqrt_denom_minus = rsqrt(denom_minus);
float2 new_dX = float2(
sqrt(F * (t + p) / (t * (q + t))),
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
sqrtA * inv_sqrt_denom_plus,
sqrtB * inv_sqrt_denom_plus * signB
);
float2 new_dY = float2(
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
sqrt(F * (t + p) / (t * (q - t)))
sqrtB * inv_sqrt_denom_minus * -signB,
sqrtA * inv_sqrt_denom_minus
);
d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | isnan(new_dY));
@@ -656,16 +706,15 @@ struct PSMain
{
dX = new_dX;
dY = new_dY;
length_x = length(dX);
length_y = length(dY);
}
}
// Compute AF values
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
bool is_major_x = squared_length_x > squared_length_y;
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
float length_major = sqrt(squared_length_major);
bool is_major_x = length_x > length_y;
float length_major = is_major_x ? length_x : length_y;
float length_minor = is_major_x ? length_y : length_x;
float aniso_ratio;
float length_lod;
@@ -677,41 +726,25 @@ struct PSMain
// Perform isotropic filtering instead.
aniso_ratio = 1.0f;
length_lod = length_major;
aniso_line = float2(0, 0);
aniso_line = float2(0.0f, 0.0f);
}
else
{
float norm_major = 1.0f / length_major;
float2 aniso_line_dir = is_major_x ? dX : dY;
float2 aniso_line_dir = float2(
(is_major_x ? dX.x : dY.x) * norm_major,
(is_major_x ? dX.y : dY.y) * norm_major
);
aniso_ratio = squared_length_major / determinant;
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
if (aniso_ratio > PS_SW_ANISO)
{
// ratio is clamped - Lod is based on ratio (preserves area)
aniso_ratio = PS_SW_ANISO;
length_lod = length_major / PS_SW_ANISO;
}
else
{
// ratio not clamped - Lod is based on area
length_lod = determinant / length_major;
}
aniso_ratio = min(length_major / length_minor, float(PS_SW_ANISO));
length_lod = length_major / aniso_ratio;
// clamp to top Lod
if (length_lod < 1.0f)
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
aniso_ratio = round(aniso_ratio);
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
}
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0;
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0.0f;
float4 colour;
if (aniso_ratio == 1.0f)
@@ -720,10 +753,11 @@ struct PSMain
}
else
{
float4 num = float4(0, 0, 0, 0);
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
float2 segment = (2.0f * aniso_line) / aniso_ratio;
for (int i = 0; i < aniso_ratio; i++)
{
float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
float2 d = -aniso_line + (0.5f + i) * segment;
float2 uv_sample = uv + d;
float4 sample_colour = sample_tex(tex_sampler, uv_sample, level(lod));
num += sample_colour;
@@ -1456,9 +1490,9 @@ struct PSMain
}
}
MainPSOut ps_main()
MainResult ps_main()
{
MainPSOut out = {};
MainResult out = {};
float input_z = in.p.z;
if (PS_ZFLOOR)
input_z = floor(input_z * 0x1p32) * 0x1p-32;
@@ -1466,15 +1500,15 @@ struct PSMain
if (PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER)
{
if (PS_ZTST == ZTST::GEQUAL && input_z < current_depth)
discard_fragment();
discard();
if (PS_ZTST == ZTST::GREATER && input_z <= current_depth)
discard_fragment();
discard();
}
if (PS_SCANMSK & 2)
{
if ((uint(in.p.y) & 1) == (PS_SCANMSK & 1))
discard_fragment();
discard();
}
if (PS_DATE >= 5)
@@ -1484,7 +1518,7 @@ struct PSMain
bool bad = PS_RTA_CORRECTION ? ((PS_DATE & 3) == 1 ? (rt_a > (254.5f / 255.f)) : (rt_a < (254.5f / 255.f))) : ((PS_DATE & 3) == 1 ? (rt_a > 0.5) : (rt_a < 0.5));
if (bad)
discard_fragment();
discard();
}
if (PS_DATE == 3)
@@ -1493,7 +1527,7 @@ struct PSMain
// Note prim_id == stencil_ceil will be the primitive that will update
// the bad alpha value so we must keep it.
if (float(prim_id) > stencil_ceil)
discard_fragment();
discard();
}
float4 C = ps_color();
@@ -1502,9 +1536,9 @@ struct PSMain
if (PS_AA1 != AA1::NONE)
{
float cov = PS_AA1 == AA1::LINE ?
saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) : // Blur only outer part of the line by scaling coverage.
saturate(1.f - abs(in.inv_cov));
float cov = PS_AA1 == AA1::LINE
? saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) // Blur only outer part of the line by scaling coverage.
: saturate(1.f - abs(in.inv_cov));
if (!PS_ABE || floor(C.a) == 128.f) // The coverage is only used if the fragment alpha is 128.
C.a = 128.f * cov;
}
@@ -1516,7 +1550,7 @@ struct PSMain
bool atst_pass = atst(C);
if (PS_AFAIL == AFAIL::KEEP && !atst_pass)
discard_fragment();
discard();
float4 alpha_blend = float4(0.f);
if (SW_AD_TO_HW)
@@ -1622,32 +1656,32 @@ struct PSMain
if (PS_AFAIL == AFAIL::RGB_ONLY_DSB)
alpha_blend.a = float(atst_pass);
if (PS_COLOR0)
if (!PS_NO_COLOR)
{
out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f;
out.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.f) : C.rgb / 255.f;
}
if (PS_COLOR1)
if (!PS_NO_COLOR1)
out.c1 = alpha_blend;
if (PS_ZCLAMP)
input_z = min(input_z, cb.max_depth);
if (PS_AA1 == AA1::TRIANGLE_SW_Z && !in.interior)
input_z = current_depth; // No depth update for triangle edges.
discard_depth(input_z); // No depth update for triangle edges.
if (!atst_pass)
{
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::RGB_ONLY)
out.c0.a = current_color.a;
out.c0.a = current_color.a; // discard alpha
else if (PS_AFAIL == AFAIL::ZB_ONLY)
out.c0 = current_color;
discard_color(out.c0);
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::FB_ONLY)
input_z = current_depth;
discard_depth(input_z);
}
out.setDepth(input_z);
out.depth = input_z;
return out;
}
@@ -1659,16 +1693,19 @@ fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]])
return in * 2;
}
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH;
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH;
constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK;
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH && !PS_ROV_COLOR;
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH && !PS_ROV_COLOR;
constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK && PS_ROV_DEPTH == ROV_DEPTH::NONE;
#else
constant bool NEEDS_RT_TEX = NEEDS_RT;
constant bool NEEDS_RT_TEX = NEEDS_RT && !PS_ROV_COLOR;
constant bool NEEDS_DS_FBF = false;
constant float ds_fbf = 0;
#endif
constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF;
constant bool NEEDS_DS_DEPTH = SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF;
constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF && PS_ROV_DEPTH == ROV_DEPTH::NONE;
constant bool NEEDS_DS_DEPTH = (SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
constant bool NEEDS_RT_ROV = PS_ROV_COLOR && !ROV_NEEDS_R32;
constant bool NEEDS_RT_U32 = PS_ROV_COLOR && ROV_NEEDS_R32;
constant bool NEEDS_DS_ROV = PS_ROV_DEPTH != ROV_DEPTH::NONE;
fragment MainPSOut ps_main(
MainPSIn in [[stage_in]],
@@ -1687,7 +1724,10 @@ fragment MainPSOut ps_main(
texture2d<float> rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]],
texture2d<float> primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]],
texture2d<float> ds_tex [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_TEX)]],
depth2d<float> ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]])
depth2d<float> ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]],
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]],
texture2d<float, access::read_write> ds_rov [[texture(GSMTLTextureIndexDepthTarget), raster_order_group(1), function_constant(NEEDS_DS_ROV)]])
{
PSMain main(in, cb);
main.tex_sampler = s;
@@ -1708,7 +1748,9 @@ fragment MainPSOut ps_main(
if (SW_DEPTH)
{
if (DEPTH_FEEDBACK)
if (PS_ROV_DEPTH != ROV_DEPTH::NONE)
main.current_depth = ds_rov.read(coord).x;
else if (DEPTH_FEEDBACK)
main.current_depth = ds_depth.read(coord);
else if (NEEDS_DS_FBF)
main.current_depth = ds_fbf < 0 ? ds_depth.read(coord) : ds_fbf;
@@ -1716,20 +1758,80 @@ fragment MainPSOut ps_main(
main.current_depth = ds_tex.read(coord).x;
}
if (NEEDS_RT)
if (NEEDS_RT || (PS_ROV_COLOR && any(cb.fbmask == 0xff)))
{
if (PS_ROV_COLOR)
{
if (ROV_NEEDS_R32)
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
else
main.current_color = rt_rov.read(coord);
}
else
{
#if FBFETCH_SUPPORT
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
#else
main.current_color = rt.read(coord);
main.current_color = rt.read(coord);
#endif
}
}
else
{
main.current_color = 0;
}
return main.ps_main();
MainResult out = main.ps_main();
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE && !main.depth_discarded)
ds_rov.write(out.depth, coord);
if (PS_ROV_COLOR && !main.color_discarded)
{
if (!PS_FBMASK)
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
if (ROV_NEEDS_R32)
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
else
rt_rov.write(out.c0, coord);
}
return out;
}
// Metal doesn't let you toggle eft with function constants so we need a separate function for it
[[early_fragment_tests]]
fragment void ps_main_rov_eft(
MainPSIn in [[stage_in]],
constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
sampler s [[sampler(0)]],
texture2d<float> tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]],
depth2d<float> depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]],
texture2d<float> palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]],
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]])
{
PSMain main(in, cb);
main.tex_sampler = s;
if (PS_TEX_IS_COLOR)
main.tex = tex;
else
main.tex_depth = depth;
if (PS_HAS_PALETTE)
main.palette = palette;
uint2 coord = uint2(in.p.xy);
if (ROV_NEEDS_R32)
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
else
main.current_color = rt_rov.read(coord);
MainPSOut out = main.ps_main();
if (!main.color_discarded)
{
if (!PS_FBMASK)
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
if (ROV_NEEDS_R32)
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
else
rt_rov.write(out.c0, coord);
}
}
#if PRIMID_SUPPORT
+1 -1
View File
@@ -1262,7 +1262,7 @@ void GSDeviceOGL::DestroyPipelineStatisticsQueries()
glEndQuery(GL_FRAGMENT_SHADER_INVOCATIONS_ARB);
}
for (int i = 0; i < m_pipeline_statistics_queries.size(); i++)
for (size_t i = 0; i < m_pipeline_statistics_queries.size(); i++)
{
glDeleteQueries(2, m_pipeline_statistics_queries[i].data());
m_pipeline_statistics_queries[i].fill(0);
+1 -1
View File
@@ -1030,7 +1030,7 @@ void GameDatabase::initDatabase()
const ryml::csubstr yaml = ryml::to_csubstr(*buffer);
Error error;
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error, true);
if (!tree.has_value())
{
Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);
+10 -24
View File
@@ -1298,9 +1298,11 @@ void FullscreenUI::DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size)
#else
localtime_r(&utc_time_t, &tm_local);
#endif
heading_str.format(FSUI_FSTR("{:%H:%M}"), tm_local);
char buf[256];
std::strftime(buf, sizeof(buf), "%X", &tm_local);
heading_str.assign(buf);
const ImVec2 time_size = heading_font.first->CalcTextSizeA(heading_font.second, FLT_MAX, 0.0f, "00:00");
const ImVec2 time_size = heading_font.first->CalcTextSizeA(heading_font.second, FLT_MAX, 0.0f, heading_str.c_str());
time_pos = ImVec2(heading_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) - time_size.x,
LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING));
ImGuiFullscreen::AddTextWithShadow(
@@ -1563,20 +1565,10 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
const float image_width = 60.0f;
const float image_height = 90.0f;
const std::string_view path_string(Path::GetFileName(s_current_disc_path));
const ImVec2 title_size(
g_large_font.first->CalcTextSizeA(g_large_font.second, std::numeric_limits<float>::max(), -1.0f, s_current_game_title.c_str()));
const ImVec2 path_size(path_string.empty() ?
ImVec2(0.0f, 0.0f) :
g_medium_font.first->CalcTextSizeA(g_medium_font.second, std::numeric_limits<float>::max(), -1.0f,
path_string.data(), path_string.data() + path_string.length()));
const ImVec2 subtitle_size(g_medium_font.first->CalcTextSizeA(
g_medium_font.second, std::numeric_limits<float>::max(), -1.0f, s_current_game_subtitle.c_str()));
ImVec2 title_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - title_size.x,
display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height));
ImVec2 path_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - path_size.x,
title_pos.y + g_large_font.second + LayoutScale(4.0f));
ImVec2 subtitle_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - subtitle_size.x,
ImVec2 title_pos(LayoutScale(10.0f + image_width + 20.0f), LayoutScale(10.0f));
ImVec2 path_pos(LayoutScale(10.0f + image_width + 20.0f), title_pos.y + g_large_font.second + LayoutScale(4.0f));
ImVec2 subtitle_pos(LayoutScale(10.0f + image_width + 20.0f),
(path_string.empty() ? title_pos.y + g_large_font.second : path_pos.y + g_medium_font.second) + LayoutScale(4.0f));
float rp_height = 0.0f;
@@ -1593,12 +1585,8 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
// Add a small extra gap if any Rich Presence is displayed
rp_height = rp_size.y - g_medium_font.second + LayoutScale(2.0f);
const ImVec2 rp_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - rp_size.x,
subtitle_pos.y + g_medium_font.second + LayoutScale(4.0f) - rp_height);
title_pos.y -= rp_height;
path_pos.y -= rp_height;
subtitle_pos.y -= rp_height;
const ImVec2 rp_pos(LayoutScale(10.0f + image_width + 20.0f),
subtitle_pos.y + g_medium_font.second + LayoutScale(4.0f));
DrawShadowedText(dl, g_medium_font, rp_pos, text_color, rp.data(), rp.data() + rp.length(), wrap_width);
}
@@ -1611,8 +1599,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
}
DrawShadowedText(dl, g_medium_font, subtitle_pos, text_color, s_current_game_subtitle.c_str());
const ImVec2 image_min(display_size.x - LayoutScale(10.0f + image_width),
display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height) - rp_height);
const ImVec2 image_min(LayoutScale(10.0f), LayoutScale(10.0f));
const ImVec2 image_max(image_min.x + LayoutScale(image_width), image_min.y + LayoutScale(image_height) + rp_height);
{
auto lock = GameList::GetLock();
@@ -4080,7 +4067,6 @@ TRANSLATE_NOOP("FullscreenUI", "Downloading covers...");
TRANSLATE_NOOP("FullscreenUI", "An error occurred while deleting empty game settings:\n{}");
TRANSLATE_NOOP("FullscreenUI", "An error occurred while saving game settings:\n{}");
TRANSLATE_NOOP("FullscreenUI", "{} is not a valid disc image.");
TRANSLATE_NOOP("FullscreenUI", "{:%H:%M}");
TRANSLATE_NOOP("FullscreenUI", "This Session: {}");
TRANSLATE_NOOP("FullscreenUI", "All Time: {}");
TRANSLATE_NOOP("FullscreenUI", "Save Slot {0}");
+2
View File
@@ -5949,6 +5949,7 @@ TRANSLATE_NOOP("FullscreenUI", "Shows the host's CPU utilization based on thread
TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU utilization.");
TRANSLATE_NOOP("FullscreenUI", "Shows indicators when fast forwarding, pausing, and other abnormal states are active.");
TRANSLATE_NOOP("FullscreenUI", "Shows debug information about the renderer.");
TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU pipeline statistics.");
TRANSLATE_NOOP("FullscreenUI", "Shows a visual history of frame times.");
TRANSLATE_NOOP("FullscreenUI", "Shows the current configuration in the bottom-right corner of the display.");
TRANSLATE_NOOP("FullscreenUI", "Shows the amount of currently active patches/cheats on the bottom-right corner of the display.");
@@ -6534,6 +6535,7 @@ TRANSLATE_NOOP("FullscreenUI", "Show CPU Usage");
TRANSLATE_NOOP("FullscreenUI", "Show GPU Usage");
TRANSLATE_NOOP("FullscreenUI", "Show Status Indicators");
TRANSLATE_NOOP("FullscreenUI", "Show GPU Debug Info");
TRANSLATE_NOOP("FullscreenUI", "Show GPU Stats");
TRANSLATE_NOOP("FullscreenUI", "Show Frame Times");
TRANSLATE_NOOP("FullscreenUI", "Show Settings");
TRANSLATE_NOOP("FullscreenUI", "Show Patches");
+2 -2
View File
@@ -607,12 +607,12 @@ bool IOPMemoryInterface::Write128(u32 address, u128 value)
return false;
}
bool IOPMemoryInterface::WriteBytes(u32 address, void* src, u32 size)
bool IOPMemoryInterface::WriteBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeWriteBytes(address, src, size);
}
bool IOPMemoryInterface::CompareBytes(u32 address, void* src, u32 size)
bool IOPMemoryInterface::CompareBytes(u32 address, const void* src, u32 size)
{
return iopMemSafeCmpBytes(address, src, size) == 0;
}
+2 -2
View File
@@ -136,7 +136,7 @@ public:
bool Write32(u32 address, u32 value) override;
bool Write64(u32 address, u64 value) override;
bool Write128(u32 address, u128 value) override;
bool WriteBytes(u32 address, void* src, u32 size) override;
bool WriteBytes(u32 address, const void* src, u32 size) override;
bool CompareBytes(u32 address, void* src, u32 size) override;
bool CompareBytes(u32 address, const void* src, u32 size) override;
};

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