mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-12 18:24:18 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e3ad282a8 | |||
| 091b3a8694 | |||
| 42ce97aca5 | |||
| a4df5c300e | |||
| 4bf51e735a | |||
| d7e7c8164c | |||
| eb5e3fcd13 | |||
| 8f8968cbb1 | |||
| ac83e93426 | |||
| 78d0cf4a4f | |||
| 2444c6090b | |||
| 7e49bc5a50 | |||
| 457719d0cc | |||
| c3e63d5a0e | |||
| 8c1b3f8ce3 | |||
| 55b37b794c | |||
| 1aca396e34 | |||
| cb000a5101 | |||
| 1ae2a96747 | |||
| 6834086c37 | |||
| b33b493c6e | |||
| 437fe1e442 | |||
| 2a348dcff9 | |||
| 2821986fb2 | |||
| 1a13f9707b | |||
| ff1b47942c | |||
| 13e4973b48 | |||
| fd46054fb7 | |||
| f618f39bee | |||
| 603486ad2f | |||
| a4b11651a4 | |||
| b6e9a04433 | |||
| 05a2bbbf35 | |||
| 16b6572826 | |||
| 806f06fba6 | |||
| 7a90caedec | |||
| 017b4d07da | |||
| 05015bc476 | |||
| 1f0f3b92b3 | |||
| fe3ce7f578 | |||
| 51c995168a | |||
| f83e4ff4bf | |||
| b67e793283 | |||
| d87ec54a5f | |||
| a6b1040caf | |||
| 9650c4b79a | |||
| 3f8eb5adf0 | |||
| 99a78f9a55 |
@@ -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
|
||||
|
||||
Regular → Executable
+4
-4
@@ -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
|
||||
|
||||
Regular → Executable
+4
-5
@@ -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';
|
||||
Vendored
+2
@@ -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,
|
||||
|
||||
Vendored
+3
@@ -1,3 +1,6 @@
|
||||
/* for syscall() */
|
||||
#define _DEFAULT_SOURCE
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
Vendored
+2
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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
@@ -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__)
|
||||
|
||||
Vendored
+49
@@ -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;
|
||||
|
||||
Vendored
+9
@@ -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) {
|
||||
|
||||
Vendored
+7
@@ -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
@@ -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++) {
|
||||
|
||||
Vendored
+4
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
+994
-43
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
+167
-340
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
+102
-30
@@ -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,63 +28,135 @@ 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);
|
||||
callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location location, void* user_data) {
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
|
||||
Error::SetString(context->error, std::string(msg, msg_len));
|
||||
std::longjmp(context->env, 1);
|
||||
if (RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data))
|
||||
{
|
||||
Error::SetString(context->error, std::string(msg, msg_len));
|
||||
std::longjmp(context->env, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string description(msg, msg_len);
|
||||
pxFailRel(description.c_str());
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
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.
|
||||
#if RYML_VERSION_MAJOR > 0 || RYML_VERSION_MINOR >= 11
|
||||
callbacks.set_user_data(nullptr);
|
||||
#else
|
||||
callbacks.m_user_data = nullptr;
|
||||
#endif
|
||||
tree.callbacks(callbacks);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -62,6 +62,7 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* settings_dialog, QWidget* p
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showUsageCPU, "EmuCore/GS", "OsdShowCPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showUsageGPU, "EmuCore/GS", "OsdShowGPU", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showDebugGPU, "EmuCore/GS", "OsdShowGPUDebug", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showStatsGPU, "EmuCore/GS", "OsdShowGPUStats", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showStatusIndicators, "EmuCore/GS", "OsdShowIndicators", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFrameTimes, "EmuCore/GS", "OsdShowFrameTimes", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showHardwareInfo, "EmuCore/GS", "OsdShowHardwareInfo", false);
|
||||
@@ -127,6 +128,8 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* settings_dialog, QWidget* p
|
||||
dialog()->registerWidgetHelp(m_ui.showDebugGPU, tr("Show GPU Debug Info"),
|
||||
tr("Unchecked"), tr("Shows debug information about the renderer."));
|
||||
#endif
|
||||
dialog()->registerWidgetHelp(m_ui.showDebugGPU, tr("Show GPU Pipeline Statistics"),
|
||||
tr("Unchecked"), tr("Shows GPU vertex shader and pixels shader invocations."));
|
||||
dialog()->registerWidgetHelp(m_ui.showFrameTimes, tr("Show Frame Times"), tr("Unchecked"),
|
||||
tr("Displays a graph showing the average frametimes."));
|
||||
|
||||
@@ -243,6 +246,7 @@ void OSDSettingsWidget::onPerformancePosChanged()
|
||||
m_ui.showGSStats->setEnabled(enabled);
|
||||
m_ui.showUsageCPU->setEnabled(enabled);
|
||||
m_ui.showUsageGPU->setEnabled(enabled);
|
||||
m_ui.showStatsGPU->setEnabled(enabled);
|
||||
m_ui.showStatusIndicators->setEnabled(enabled);
|
||||
m_ui.showFrameTimes->setEnabled(enabled);
|
||||
m_ui.showHardwareInfo->setEnabled(enabled);
|
||||
@@ -265,6 +269,7 @@ void OSDSettingsWidget::setAllCheckboxes(bool checked)
|
||||
m_ui.showGSStats,
|
||||
m_ui.showUsageCPU,
|
||||
m_ui.showUsageGPU,
|
||||
m_ui.showStatsGPU,
|
||||
m_ui.showFrameTimes,
|
||||
m_ui.showHardwareInfo,
|
||||
m_ui.showVersion,
|
||||
|
||||
@@ -492,6 +492,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QCheckBox" name="showStatsGPU">
|
||||
<property name="text">
|
||||
<string>Show GPU Pipeline Statistics</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -530,6 +537,7 @@
|
||||
<tabstop>showGSStats</tabstop>
|
||||
<tabstop>showUsageCPU</tabstop>
|
||||
<tabstop>showUsageGPU</tabstop>
|
||||
<tabstop>showStatsGPU</tabstop>
|
||||
<tabstop>showStatusIndicators</tabstop>
|
||||
<tabstop>showFrameTimes</tabstop>
|
||||
</tabstops>
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -66,6 +66,7 @@ private:
|
||||
void setupGameListPage();
|
||||
void setupControllerPage();
|
||||
void setupRetroAchievementsPage();
|
||||
void setupCompletePage();
|
||||
void refreshRetroAchievementsLoginState();
|
||||
|
||||
void pageChangedTo(int page);
|
||||
|
||||
@@ -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><html><head/><body><h1 style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:xx-large; font-weight:700;">Welcome to PCSX2!</span></h1><p>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 <a href="https://pcsx2.net/docs/">here</a>.</p><p>By default, PCSX2 will connect to the server at <a href="https://pcsx2.net/">pcsx2.net</a> to check for updates, and if available and confirmed, download update packages from <a href="https://github.com/">github.com</a>. 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.</p><p>Please choose a language and theme to begin.</p></body></html></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><html><head/><body><p>PCSX2 requires a PS2 BIOS in order to run.</p><p>For legal reasons, you must obtain a BIOS <strong>from an actual PS2 unit that you own</strong> (borrowing doesn't count).</p><p>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.</p><p>A guide for dumping your BIOS can be found <a href="https://pcsx2.net/docs/setup/bios/">here</a>.</p></body></html></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><html><head/><body><p>PCSX2 will automatically scan and identify games from the selected directories below, and populate the game list.<br>These games should be dumped from discs you own. Guides for dumping discs can be found <a href="https://pcsx2.net/docs/setup/dumping">here</a>.</p><p>Supported formats for dumps include:</p><p><ul><li>.bin/.iso (ISO Disc Images)</li><li>.mdf (Media Descriptor File)</li><li>.chd (Compressed Hunks of Data)</li><li>.cso (Compressed ISO)</li><li>.zso (Compressed ISO)</li><li>.gz (Gzip Compressed ISO)</li></ul></p></p></body></html></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><html><head/><body><p>By default, PCSX2 will map your keyboard to the virtual PS2 controller.</p><p><span style=" font-weight:704;">To use an external controller, you must map it first. </span>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.</p><p>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.</p><p>Guides for configuring controllers can be found <a href="https://pcsx2.net/docs/post/controllers/"><span style="">here</span></a>.</p></body></html></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><html><head/><body><h1 style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:xx-large; font-weight:700;">Setup Complete!</span></h1><p>You are now ready to run games.</p><p><span style=" font-weight:700;">Warning: Do not run untrusted programs in PCSX2. It is not a sandbox and cannot protect your computer from malicious software.</span></p><p>Further options are available under the settings menu. You can also use the Big Picture UI for navigation entirely with a gamepad.</p><p>We hope you enjoy using PCSX2.</p></body></html></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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1801
-1883
File diff suppressed because it is too large
Load Diff
+35
-24
@@ -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)
|
||||
{
|
||||
|
||||
@@ -774,6 +774,7 @@ struct Pcsx2Config
|
||||
OsdShowCPU : 1,
|
||||
OsdShowGPU : 1,
|
||||
OsdShowGPUDebug : 1,
|
||||
OsdShowGPUStats : 1,
|
||||
OsdShowIndicators : 1,
|
||||
OsdShowFrameTimes : 1,
|
||||
OsdShowHardwareInfo : 1,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -163,6 +163,8 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
|
||||
|
||||
if (!g_gs_device->SetGPUTimingEnabled(true))
|
||||
GSConfig.OsdShowGPU = false;
|
||||
if (!g_gs_device->SetGPUPipelineStatisticsEnabled(true))
|
||||
GSConfig.OsdShowGPUStats = false;
|
||||
|
||||
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
|
||||
Console.WriteLn(g_gs_device->GetDriverInfo());
|
||||
@@ -884,6 +886,12 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
if (!g_gs_device->SetGPUTimingEnabled(true))
|
||||
GSConfig.OsdShowGPU = false;
|
||||
}
|
||||
|
||||
if (GSConfig.OsdShowGPUStats != old_config.OsdShowGPUStats)
|
||||
{
|
||||
if (!g_gs_device->SetGPUPipelineStatisticsEnabled(GSConfig.OsdShowGPUStats))
|
||||
GSConfig.OsdShowGPUStats = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GSSetSoftwareRendering(bool software_renderer, GSInterlaceMode new_interlace)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -29,6 +29,12 @@ static inline constexpr Filter BilnIf(bool biln)
|
||||
return biln ? Biln : Nearest;
|
||||
}
|
||||
|
||||
struct GPUPipelineStatistics
|
||||
{
|
||||
u64 vs_invocations;
|
||||
u64 ps_invocations;
|
||||
};
|
||||
|
||||
enum class ShaderConvert
|
||||
{
|
||||
COPY = 0,
|
||||
@@ -893,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
|
||||
@@ -1610,6 +1616,12 @@ public:
|
||||
/// Returns the amount of GPU time utilized since the last time this method was called.
|
||||
virtual float GetAndResetAccumulatedGPUTime() = 0;
|
||||
|
||||
/// Enables/disables GPU pipeline statistics.
|
||||
virtual bool SetGPUPipelineStatisticsEnabled(bool enabled) = 0;
|
||||
|
||||
/// Get the pipeline statistics for the last frame.
|
||||
virtual GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() = 0;
|
||||
|
||||
/// Returns true if not enough time has passed for present to not block.
|
||||
bool ShouldSkipPresentingFrame();
|
||||
|
||||
|
||||
@@ -700,7 +700,9 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
|
||||
EndPresentFrame();
|
||||
|
||||
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
|
||||
const float gpu_time = g_gs_device->GetAndResetAccumulatedGPUTime();
|
||||
GPUPipelineStatistics gpu_stats = g_gs_device->GetAndResetAccumulatedGPUPipelineStatistics();
|
||||
PerformanceMetrics::OnGPUPresent(gpu_time, gpu_stats.vs_invocations, gpu_stats.ps_invocations);
|
||||
}
|
||||
|
||||
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
|
||||
|
||||
@@ -618,6 +618,7 @@ void GSDevice11::Destroy()
|
||||
GSDevice::Destroy();
|
||||
DestroySwapChain();
|
||||
DestroyTimestampQueries();
|
||||
DestroyPipelineStatisticsQueries();
|
||||
|
||||
m_convert = {};
|
||||
m_present = {};
|
||||
@@ -1082,6 +1083,10 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
|
||||
if (m_vsync_mode == GSVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
// Get the pipeline statistics for this frame before postprocessing.
|
||||
if (m_gpu_pipeline_statistics_enabled)
|
||||
PopPipelineStatisticsQuery();
|
||||
|
||||
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
|
||||
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
|
||||
if (m_state.rtv)
|
||||
@@ -1113,6 +1118,9 @@ void GSDevice11::EndPresent()
|
||||
if (m_vsync_mode != GSVSyncMode::FIFO && m_gpu_timing_enabled)
|
||||
PopTimestampQuery();
|
||||
|
||||
if (m_gpu_pipeline_statistics_enabled)
|
||||
PopPipelineStatisticsQuery();
|
||||
|
||||
// clear out the swap chain view, it might get resized..
|
||||
OMSetRenderTargets(nullptr, nullptr, nullptr);
|
||||
|
||||
@@ -1122,6 +1130,9 @@ void GSDevice11::EndPresent()
|
||||
|
||||
if (m_gpu_timing_enabled)
|
||||
KickTimestampQuery();
|
||||
|
||||
if (m_gpu_pipeline_statistics_enabled)
|
||||
KickPipelineStatisticsQuery();
|
||||
}
|
||||
|
||||
bool GSDevice11::CreateTimestampQueries()
|
||||
@@ -1242,6 +1253,95 @@ float GSDevice11::GetAndResetAccumulatedGPUTime()
|
||||
return value;
|
||||
}
|
||||
|
||||
bool GSDevice11::CreatePipelineStatisticsQueries()
|
||||
{
|
||||
for (u32 i = 0; i < NUM_PIPELINE_STATISTICS_QUERIES; i++)
|
||||
{
|
||||
const CD3D11_QUERY_DESC qdesc(D3D11_QUERY_PIPELINE_STATISTICS);
|
||||
const HRESULT hr = m_dev->CreateQuery(&qdesc, m_pipeline_statistics_queries[i].put());
|
||||
if (FAILED(hr))
|
||||
{
|
||||
m_pipeline_statistics_queries = {};
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
KickPipelineStatisticsQuery();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::KickPipelineStatisticsQuery()
|
||||
{
|
||||
if (m_pipeline_statistics_query_started || !m_pipeline_statistics_queries[0] ||
|
||||
m_waiting_pipeline_statistics_queries == NUM_PIPELINE_STATISTICS_QUERIES)
|
||||
return;
|
||||
|
||||
m_ctx->Begin(m_pipeline_statistics_queries[m_write_pipeline_statistics_query].get());
|
||||
m_pipeline_statistics_query_started = true;
|
||||
}
|
||||
|
||||
void GSDevice11::DestroyPipelineStatisticsQueries()
|
||||
{
|
||||
if (!m_pipeline_statistics_queries[0])
|
||||
return;
|
||||
|
||||
if (m_pipeline_statistics_query_started)
|
||||
m_ctx->End(m_pipeline_statistics_queries[m_write_timestamp_query].get());
|
||||
|
||||
m_timestamp_queries = {};
|
||||
m_read_timestamp_query = 0;
|
||||
m_write_timestamp_query = 0;
|
||||
m_waiting_timestamp_queries = 0;
|
||||
m_timestamp_query_started = 0;
|
||||
}
|
||||
|
||||
void GSDevice11::PopPipelineStatisticsQuery()
|
||||
{
|
||||
while (m_waiting_pipeline_statistics_queries > 0)
|
||||
{
|
||||
D3D11_QUERY_DATA_PIPELINE_STATISTICS stats{};
|
||||
const HRESULT stats_hr = m_ctx->GetData(
|
||||
m_pipeline_statistics_queries[m_read_pipeline_statistics_query].get(), &stats, sizeof(stats), 0);
|
||||
if (stats_hr != S_OK)
|
||||
break;
|
||||
|
||||
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.VSInvocations;
|
||||
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.PSInvocations;
|
||||
m_read_pipeline_statistics_query = (m_read_pipeline_statistics_query + 1) % NUM_PIPELINE_STATISTICS_QUERIES;
|
||||
m_waiting_pipeline_statistics_queries--;
|
||||
}
|
||||
|
||||
if (m_pipeline_statistics_query_started)
|
||||
{
|
||||
m_ctx->End(m_pipeline_statistics_queries[m_write_pipeline_statistics_query].get());
|
||||
m_write_pipeline_statistics_query = (m_write_pipeline_statistics_query + 1) % NUM_PIPELINE_STATISTICS_QUERIES;
|
||||
m_pipeline_statistics_query_started = false;
|
||||
m_waiting_pipeline_statistics_queries++;
|
||||
}
|
||||
}
|
||||
|
||||
GPUPipelineStatistics GSDevice11::GetAndResetAccumulatedGPUPipelineStatistics()
|
||||
{
|
||||
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
|
||||
m_accumulated_gpu_pipeline_statistics = {};
|
||||
return stats;
|
||||
}
|
||||
|
||||
bool GSDevice11::SetGPUPipelineStatisticsEnabled(bool enabled)
|
||||
{
|
||||
m_gpu_pipeline_statistics_enabled = enabled;
|
||||
|
||||
if (m_gpu_pipeline_statistics_enabled)
|
||||
{
|
||||
return CreatePipelineStatisticsQueries();
|
||||
}
|
||||
else
|
||||
{
|
||||
DestroyPipelineStatisticsQueries();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::DrawPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
|
||||
@@ -93,6 +93,7 @@ private:
|
||||
VERTEX_BUFFER_SIZE = 32 * 1024 * 1024,
|
||||
INDEX_BUFFER_SIZE = 16 * 1024 * 1024,
|
||||
NUM_TIMESTAMP_QUERIES = 5,
|
||||
NUM_PIPELINE_STATISTICS_QUERIES = 5,
|
||||
};
|
||||
|
||||
void SetFeatures(IDXGIAdapter1* adapter);
|
||||
@@ -107,6 +108,11 @@ private:
|
||||
void PopTimestampQuery();
|
||||
void KickTimestampQuery();
|
||||
|
||||
bool CreatePipelineStatisticsQueries();
|
||||
void DestroyPipelineStatisticsQueries();
|
||||
void PopPipelineStatisticsQuery();
|
||||
void KickPipelineStatisticsQuery();
|
||||
|
||||
void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, u32 c, const Filter filter) override;
|
||||
void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, Filter filter, const InterlaceConstantBuffer& cb) override;
|
||||
void DoFXAA(GSTexture* sTex, GSTexture* dTex) override;
|
||||
@@ -192,6 +198,14 @@ private:
|
||||
bool m_timestamp_query_started = false;
|
||||
bool m_gpu_timing_enabled = false;
|
||||
|
||||
std::array<wil::com_ptr_nothrow<ID3D11Query>, NUM_PIPELINE_STATISTICS_QUERIES> m_pipeline_statistics_queries = {};
|
||||
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics{};
|
||||
u8 m_read_pipeline_statistics_query = 0;
|
||||
u8 m_write_pipeline_statistics_query = 0;
|
||||
u8 m_waiting_pipeline_statistics_queries = 0;
|
||||
bool m_gpu_pipeline_statistics_enabled = false;
|
||||
bool m_pipeline_statistics_query_started = false;
|
||||
|
||||
struct
|
||||
{
|
||||
wil::com_ptr_nothrow<ID3D11InputLayout> il;
|
||||
@@ -325,6 +339,9 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
|
||||
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
|
||||
|
||||
// Helpers and utility draws.
|
||||
void DrawPrimitive();
|
||||
void DrawIndexedPrimitive();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -519,6 +519,39 @@ void GSDevice12::MoveToNextCommandList()
|
||||
m_current_command_list * NUM_TIMESTAMP_QUERIES_PER_CMDLIST);
|
||||
}
|
||||
|
||||
if (res.pipeline_statistics_query == QueryState::Ready)
|
||||
{
|
||||
// Collect the pipeline statistics from the last time this cmdlist was used.
|
||||
res.pipeline_statistics_query = QueryState::None;
|
||||
const u32 offset = (m_current_command_list * sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS));
|
||||
const D3D12_RANGE read_range = { offset, offset + sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS) };
|
||||
void* map;
|
||||
HRESULT hr = m_pipeline_statistics_query_buffer->Map(0, &read_range, &map);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
D3D12_QUERY_DATA_PIPELINE_STATISTICS stats;
|
||||
std::memcpy(&stats, static_cast<const u8*>(map) + offset, sizeof(stats));
|
||||
|
||||
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.VSInvocations;
|
||||
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.PSInvocations;
|
||||
|
||||
const D3D12_RANGE write_range = {};
|
||||
m_pipeline_statistics_query_buffer->Unmap(0, &write_range);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Warning("D3D12: Map() for pipeline statistics query failed: %08X", hr);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_gpu_pipeline_statistics_enabled)
|
||||
{
|
||||
pxAssert(res.pipeline_statistics_query == QueryState::None);
|
||||
res.pipeline_statistics_query = QueryState::Querying;
|
||||
res.command_lists[1].list4->BeginQuery(
|
||||
m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
|
||||
}
|
||||
|
||||
ID3D12DescriptorHeap* heaps[2] = {
|
||||
res.descriptor_allocator.GetDescriptorHeap(), res.sampler_allocator.GetDescriptorHeap()};
|
||||
res.command_lists[1].list4->SetDescriptorHeaps(std::size(heaps), heaps);
|
||||
@@ -564,6 +597,18 @@ bool GSDevice12::ExecuteCommandList(WaitType wait_for_completion)
|
||||
m_timestamp_query_buffer.get(), m_current_command_list * (sizeof(u64) * NUM_TIMESTAMP_QUERIES_PER_CMDLIST));
|
||||
}
|
||||
|
||||
if ((res.pipeline_statistics_query == QueryState::Querying) || (res.pipeline_statistics_query == QueryState::Ready))
|
||||
{
|
||||
if (res.pipeline_statistics_query == QueryState::Querying)
|
||||
{
|
||||
// Didn't end query in BeginPresent() so end it here.
|
||||
res.pipeline_statistics_query = QueryState::Ready;
|
||||
res.command_lists[1].list4->EndQuery(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
|
||||
}
|
||||
res.command_lists[1].list4->ResolveQueryData(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS,
|
||||
m_current_command_list, 1, m_pipeline_statistics_query_buffer.get(), m_current_command_list * sizeof(D3D11_QUERY_DATA_PIPELINE_STATISTICS));
|
||||
}
|
||||
|
||||
if (res.init_command_list_used)
|
||||
{
|
||||
hr = res.command_lists[0].list4->Close();
|
||||
@@ -716,6 +761,36 @@ void GSDevice12::WaitForGPUIdle()
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice12::CreatePipelineStatisticsQuery()
|
||||
{
|
||||
constexpr u32 QUERY_COUNT = NUM_COMMAND_LISTS;
|
||||
constexpr u32 BUFFER_SIZE = sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS) * QUERY_COUNT;
|
||||
|
||||
const D3D12_QUERY_HEAP_DESC desc = { D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS, QUERY_COUNT };
|
||||
HRESULT hr = m_device->CreateQueryHeap(&desc, IID_PPV_ARGS(m_pipeline_statistics_query_heap.put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("D3D12: CreateQueryHeap() for pipeline statistics failed with %08X", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
const D3D12MA::ALLOCATION_DESC allocation_desc = { D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_READBACK };
|
||||
const D3D12_RESOURCE_DESCU resource_desc = { {D3D12_RESOURCE_DIMENSION_BUFFER, 0, BUFFER_SIZE, 1, 1, 1,
|
||||
DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE} };
|
||||
if (m_enhanced_barriers)
|
||||
hr = m_allocator->CreateResource3(&allocation_desc, &resource_desc.desc1, D3D12_BARRIER_LAYOUT_UNDEFINED, nullptr,
|
||||
0, nullptr, m_pipeline_statistics_query_allocation.put(), IID_PPV_ARGS(m_pipeline_statistics_query_buffer.put()));
|
||||
else
|
||||
hr = m_allocator->CreateResource(&allocation_desc, &resource_desc.desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
|
||||
m_pipeline_statistics_query_allocation.put(), IID_PPV_ARGS(m_pipeline_statistics_query_buffer.put()));
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("D3D12: CreateResource() for pipeline statistics failed with %08X", hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice12::CreateTimestampQuery()
|
||||
{
|
||||
constexpr u32 QUERY_COUNT = NUM_TIMESTAMP_QUERIES_PER_CMDLIST * NUM_COMMAND_LISTS;
|
||||
@@ -769,6 +844,19 @@ bool GSDevice12::SetGPUTimingEnabled(bool enabled)
|
||||
return true;
|
||||
}
|
||||
|
||||
GPUPipelineStatistics GSDevice12::GetAndResetAccumulatedGPUPipelineStatistics()
|
||||
{
|
||||
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
|
||||
m_accumulated_gpu_pipeline_statistics = {};
|
||||
return stats;
|
||||
}
|
||||
|
||||
bool GSDevice12::SetGPUPipelineStatisticsEnabled(bool enabled)
|
||||
{
|
||||
m_gpu_pipeline_statistics_enabled = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDevice12::AllocatePreinitializedGPUBuffer(u32 size, ID3D12Resource** gpu_buffer,
|
||||
D3D12MA::Allocation** gpu_allocation, const std::function<void(void*)>& fill_callback)
|
||||
{
|
||||
@@ -863,7 +951,7 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
|
||||
m_name = D3D::GetAdapterName(m_adapter.get());
|
||||
|
||||
if (!CreateDescriptorHeaps() || !CreateCommandLists() || !CreateTimestampQuery())
|
||||
if (!CreateDescriptorHeaps() || !CreateCommandLists() || !CreateTimestampQuery() || !CreatePipelineStatisticsQuery())
|
||||
return false;
|
||||
|
||||
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
|
||||
@@ -1275,6 +1363,14 @@ GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
|
||||
return PresentResult::FrameSkipped;
|
||||
}
|
||||
|
||||
// End the pipeline statistics for this cmdlist before postprocessing.
|
||||
CommandListResources& res = m_command_lists[m_current_command_list];
|
||||
if (res.pipeline_statistics_query == QueryState::Querying)
|
||||
{
|
||||
res.pipeline_statistics_query = QueryState::Ready;
|
||||
res.command_lists[1].list4->EndQuery(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
|
||||
}
|
||||
|
||||
GSTexture12* swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer].get();
|
||||
|
||||
const D3D12CommandList& cmdlist = GetCommandList();
|
||||
@@ -3091,6 +3187,8 @@ void GSDevice12::DestroyResources()
|
||||
|
||||
m_timestamp_query_buffer.reset();
|
||||
m_timestamp_query_allocation.reset();
|
||||
m_pipeline_statistics_query_buffer.reset();
|
||||
m_pipeline_statistics_query_allocation.reset();
|
||||
m_sampler_heap_manager.Destroy();
|
||||
m_dsv_heap_manager.Destroy();
|
||||
m_rtv_heap_manager.Destroy();
|
||||
@@ -3528,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)
|
||||
@@ -3577,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)
|
||||
@@ -3601,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3679,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;
|
||||
@@ -4431,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);
|
||||
|
||||
@@ -191,6 +191,14 @@ public:
|
||||
void UploadIndices(D3D12StreamBuffer& buffer, const void* index, size_t count);
|
||||
|
||||
private:
|
||||
// For pipeline statistics
|
||||
enum class QueryState
|
||||
{
|
||||
None,
|
||||
Querying,
|
||||
Ready,
|
||||
};
|
||||
|
||||
struct CommandListResources
|
||||
{
|
||||
std::array<ComPtr<ID3D12CommandAllocator>, 2> command_allocators;
|
||||
@@ -202,6 +210,7 @@ private:
|
||||
u64 ready_fence_value = 0;
|
||||
bool init_command_list_used = false;
|
||||
bool has_timestamp_query = false;
|
||||
QueryState pipeline_statistics_query = QueryState::None;
|
||||
};
|
||||
|
||||
void LoadAgilitySDK();
|
||||
@@ -210,6 +219,7 @@ private:
|
||||
bool CreateDescriptorHeaps();
|
||||
bool CreateCommandLists();
|
||||
bool CreateTimestampQuery();
|
||||
bool CreatePipelineStatisticsQuery();
|
||||
void MoveToNextCommandList();
|
||||
void DestroyPendingResources(CommandListResources& cmdlist);
|
||||
|
||||
@@ -234,6 +244,12 @@ private:
|
||||
bool m_gpu_timing_enabled = false;
|
||||
bool m_programmable_sample_positions = false;
|
||||
|
||||
ComPtr<ID3D12QueryHeap> m_pipeline_statistics_query_heap;
|
||||
ComPtr<ID3D12Resource> m_pipeline_statistics_query_buffer;
|
||||
ComPtr<D3D12MA::Allocation> m_pipeline_statistics_query_allocation;
|
||||
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics{};
|
||||
bool m_gpu_pipeline_statistics_enabled = false;
|
||||
|
||||
D3D12DescriptorHeapManager m_descriptor_heap_manager;
|
||||
D3D12DescriptorHeapManager m_rtv_heap_manager;
|
||||
D3D12DescriptorHeapManager m_dsv_heap_manager;
|
||||
@@ -510,6 +526,9 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
|
||||
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
|
||||
|
||||
void PushDebugGroup(const char* fmt, ...) override;
|
||||
void PopDebugGroup() override;
|
||||
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -414,6 +423,9 @@ public:
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
void AccumulateCommandBufferTime(id<MTLCommandBuffer> buffer);
|
||||
|
||||
bool SetGPUPipelineStatisticsEnabled(bool enabled) override { return false; }
|
||||
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override { return {}; }
|
||||
|
||||
std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override;
|
||||
|
||||
void ClearSamplerCache() override;
|
||||
@@ -439,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);
|
||||
@@ -452,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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user