mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-11 17:54:20 +02:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
+158
-333
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;
|
||||
};
|
||||
|
||||
+88
-26
@@ -17,7 +17,7 @@ struct RapidYAMLContext
|
||||
Error* error = nullptr;
|
||||
};
|
||||
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error)
|
||||
std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr file_name, Error* error, bool resolve_anchors)
|
||||
{
|
||||
RapidYAMLContext context;
|
||||
context.error = error;
|
||||
@@ -28,40 +28,93 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
|
||||
callbacks.set_user_data(static_cast<void*>(&context));
|
||||
|
||||
callbacks.set_error_basic([](ryml::csubstr msg, const ryml::ErrorDataBasic& errdata, void* user_data) {
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_basic_format(std::move(callback), msg, errdata);
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
// This scope needs to stay, so all objects destruct before std::longjump
|
||||
{
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_basic_format(std::move(callback), msg, errdata);
|
||||
|
||||
// We might have already returned, so don't try to recover.
|
||||
pxFailRel(description.c_str());
|
||||
std::abort();
|
||||
if (context != nullptr)
|
||||
{
|
||||
Error::SetString(context->error, std::move(description));
|
||||
}
|
||||
else
|
||||
{
|
||||
pxFailRel(description.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (context != nullptr)
|
||||
{
|
||||
std::longjmp(context->env, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
});
|
||||
|
||||
callbacks.set_error_parse([](ryml::csubstr msg, const ryml::ErrorDataParse& errdata, void* user_data) {
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
// This scope needs to stay, so all objects destruct before std::longjump
|
||||
{
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_parse_format(std::move(callback), msg, errdata);
|
||||
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_parse_format(std::move(callback), msg, errdata);
|
||||
if (context != nullptr)
|
||||
{
|
||||
Error::SetString(context->error, std::move(description));
|
||||
}
|
||||
else
|
||||
{
|
||||
pxFailRel(description.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Error::SetString(context->error, std::move(description));
|
||||
std::longjmp(context->env, 1);
|
||||
if (context != nullptr)
|
||||
{
|
||||
std::longjmp(context->env, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
});
|
||||
|
||||
callbacks.set_error_visit([](ryml::csubstr msg, const ryml::ErrorDataVisit& errdata, void* user_data) {
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_visit_format(std::move(callback), msg, errdata);
|
||||
RapidYAMLContext* context = static_cast<RapidYAMLContext*>(user_data);
|
||||
// This scope needs to stay, so all objects destruct before std::longjump
|
||||
{
|
||||
std::string description;
|
||||
auto callback = [&description](ryml::csubstr string) {
|
||||
description.append(string.str, string.len);
|
||||
};
|
||||
ryml::err_visit_format(std::move(callback), msg, errdata);
|
||||
|
||||
// We've probably already returned, so don't try to recover.
|
||||
pxFailRel(description.c_str());
|
||||
std::abort();
|
||||
if (context != nullptr)
|
||||
{
|
||||
Error::SetString(context->error, std::move(description));
|
||||
}
|
||||
else
|
||||
{
|
||||
pxFailRel(description.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (context != nullptr)
|
||||
{
|
||||
std::longjmp(context->env, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::terminate();
|
||||
}
|
||||
});
|
||||
#else
|
||||
callbacks.m_user_data = static_cast<void*>(&context);
|
||||
@@ -76,15 +129,24 @@ std::optional<ryml::Tree> ParseYAMLFromString(ryml::csubstr yaml, ryml::csubstr
|
||||
ryml::EventHandlerTree event_handler(callbacks);
|
||||
ryml::Parser parser(&event_handler);
|
||||
|
||||
ryml::Tree tree;
|
||||
ryml::Tree tree(callbacks);
|
||||
|
||||
// The only options RapidYAML provides for recovering from errors are
|
||||
// throwing an exception or using setjmp/longjmp. Since we have exceptions
|
||||
// disabled we have to use the latter option.
|
||||
if (setjmp(context.env))
|
||||
if (setjmp(context.env) != 0)
|
||||
return std::nullopt;
|
||||
|
||||
ryml::parse_in_arena(&parser, file_name, yaml, &tree);
|
||||
if (resolve_anchors)
|
||||
{
|
||||
tree.resolve();
|
||||
}
|
||||
|
||||
// Callbacks passed to ryml::Tree are used for value parsing errors later,
|
||||
// so we need to clear the context before it goes out of scope.
|
||||
callbacks.set_user_data(nullptr);
|
||||
tree.callbacks(callbacks);
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
+3
-1
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1701
-1671
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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -899,7 +899,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
|
||||
__fi bool HasDepthROV() const
|
||||
{
|
||||
return rov_depth == PS_ROV_DEPTH::READ_ONLY || rov_depth == PS_ROV_DEPTH::READ_WRITE;
|
||||
return rov_depth != PS_ROV_DEPTH::NONE;
|
||||
}
|
||||
|
||||
__fi bool HasDepthROVWrite() const
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3626,7 +3626,7 @@ void GSDevice12::PSSetShaderResource(int i, GSTexture* sr, bool check_state, Res
|
||||
}
|
||||
else
|
||||
{
|
||||
handle = m_null_texture->GetSRVDescriptor();
|
||||
handle = GetResourceDescriptor(m_null_texture.get(), type);
|
||||
}
|
||||
|
||||
if (m_tfx_textures[i] == handle)
|
||||
@@ -3675,7 +3675,7 @@ void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool wri
|
||||
else
|
||||
{
|
||||
// Unbind to avoid conflicts with OM targets.
|
||||
PSSetShaderResource(TEXTURE_RT_UAV, nullptr, false);
|
||||
PSSetShaderResource(TEXTURE_RT_UAV, nullptr, false, ResourceType::UAV);
|
||||
}
|
||||
|
||||
if (d12Ds)
|
||||
@@ -3699,7 +3699,7 @@ void GSDevice12::PSSetROVs(GSTexture* rt, GSTexture* ds, bool write_rt, bool wri
|
||||
else
|
||||
{
|
||||
// Unbind to avoid conflicts with OM targets.
|
||||
PSSetShaderResource(TEXTURE_DEPTH_UAV, nullptr, false);
|
||||
PSSetShaderResource(TEXTURE_DEPTH_UAV, nullptr, false, ResourceType::UAV);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3777,7 +3777,8 @@ void GSDevice12::UnbindTexture(GSTexture12* tex)
|
||||
// RT / primid / depth
|
||||
for (u32 i = TEXTURE_RT; i <= TEXTURE_DEPTH; i++)
|
||||
{
|
||||
if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor())
|
||||
if (m_tfx_textures[i] == tex->GetSRVDescriptor() || m_tfx_textures[i] == tex->GetFBLDescriptor() ||
|
||||
m_tfx_textures[i] == tex->GetUAVDescriptor())
|
||||
{
|
||||
m_tfx_textures[i] = m_null_texture->GetSRVDescriptor();
|
||||
m_dirty_flags |= DIRTY_FLAG_TFX_RT_TEXTURES;
|
||||
@@ -4529,22 +4530,19 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
(draw_ds && static_cast<GSTexture12*>(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0])))
|
||||
PSSetShaderResource(TEXTURE_TEXTURE, nullptr, false);
|
||||
|
||||
if (InRenderPass() && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
|
||||
// Avoid restarting the render pass just to switch from rt+depth to rt and vice versa.
|
||||
// Keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth.
|
||||
if (InRenderPass() && !(draw_rt || draw_rt_rov) && draw_ds && m_current_render_target && config.tex != m_current_render_target &&
|
||||
m_current_render_target->GetSize() == draw_ds->GetSize())
|
||||
{
|
||||
// avoid restarting the render pass just to switch from rt+depth to rt and vice versa
|
||||
// keep the depth even if doing colclip hw draws, because the next draw will probably re-enable depth
|
||||
if (!(draw_rt || draw_rt_rov) && m_current_render_target && config.tex != m_current_render_target &&
|
||||
draw_ds && m_current_render_target->GetSize() == draw_ds->GetSize())
|
||||
{
|
||||
draw_rt = m_current_render_target;
|
||||
m_pipeline_selector.rt = true;
|
||||
}
|
||||
else if (!(draw_ds || draw_ds_rov) && m_current_depth_target && config.tex != m_current_depth_target &&
|
||||
draw_rt && m_current_depth_target->GetSize() == draw_rt->GetSize())
|
||||
{
|
||||
draw_ds = m_current_depth_target;
|
||||
m_pipeline_selector.ds = true;
|
||||
}
|
||||
draw_rt = m_current_render_target;
|
||||
m_pipeline_selector.rt = true;
|
||||
}
|
||||
else if (InRenderPass() && !(draw_ds || draw_ds_rov) && draw_rt && m_current_depth_target && config.tex != m_current_depth_target &&
|
||||
m_current_depth_target->GetSize() == draw_rt->GetSize())
|
||||
{
|
||||
draw_ds = m_current_depth_target;
|
||||
m_pipeline_selector.ds = true;
|
||||
}
|
||||
|
||||
GSTexture12* draw_ds_as_rt = static_cast<GSTexture12*>(m_ds_as_rt);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -442,6 +451,7 @@ public:
|
||||
void MRESetDSS(id<MTLDepthStencilState> dss);
|
||||
void MRESetSampler(SamplerSelector sel);
|
||||
void MRESetTexture(GSTexture* tex, int pos);
|
||||
void MRESetTexture(id<MTLTexture> tex, int pos);
|
||||
void MRESetVertices(id<MTLBuffer> buffer, size_t offset);
|
||||
void MRESetVSIndices(id<MTLBuffer> buffer, size_t offset);
|
||||
void MRESetScissor(const GSVector4i& scissor);
|
||||
@@ -455,6 +465,7 @@ public:
|
||||
// MARK: Render HW
|
||||
|
||||
void SetupDestinationAlpha(GSTexture* rt, GSTexture* ds, const GSVector4i& r, SetDATM datm);
|
||||
void PrepareROVTexture(GSTexture** ptex);
|
||||
void RenderHW(GSHWDrawConfig& config) override;
|
||||
void SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder> enc, id<MTLBuffer> buffer, size_t off,
|
||||
bool one_barrier, bool full_barrier);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
// Uploads/downloads need 32-byte alignment for AVX2.
|
||||
static constexpr u32 PITCH_ALIGNMENT = 32;
|
||||
|
||||
GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, Usage usage, Format format)
|
||||
GSTextureMTL::GSTextureMTL(GSDeviceMTL* dev, MRCOwned<id<MTLTexture>> texture, MRCOwned<id<MTLTexture>> rov_texture, Usage usage, Format format)
|
||||
: m_dev(dev)
|
||||
, m_texture(std::move(texture))
|
||||
, m_rov_texture(std::move(rov_texture))
|
||||
{
|
||||
m_usage = usage;
|
||||
m_format = format;
|
||||
@@ -141,6 +142,8 @@ void GSTextureMTL::SetDebugName(std::string_view name)
|
||||
length:static_cast<NSUInteger>(name.length())
|
||||
encoding:NSUTF8StringEncoding];
|
||||
[m_texture setLabel:label];
|
||||
if (m_rov_texture)
|
||||
[m_rov_texture setLabel:[label stringByAppendingString:@".ROV"]];
|
||||
}
|
||||
|
||||
m_debug_name = std::move(name);
|
||||
|
||||
@@ -13,6 +13,7 @@ constant uint SHUFFLE_READWRITE = 3;
|
||||
|
||||
constant bool HAS_FBFETCH [[function_constant(GSMTLConstantIndex_FRAMEBUFFER_FETCH)]];
|
||||
constant bool DEPTH_FEEDBACK [[function_constant(GSMTLConstantIndex_DEPTH_FEEDBACK)]];
|
||||
constant bool ROV_NEEDS_R32 [[function_constant(GSMTLConstantIndex_ROV_NEEDS_R32)]];
|
||||
constant bool FST [[function_constant(GSMTLConstantIndex_FST)]];
|
||||
constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]];
|
||||
constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]];
|
||||
@@ -75,17 +76,21 @@ constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SC
|
||||
constant uint PS_AA1_RAW [[function_constant(GSMTLConstantIndex_PS_AA1)]];
|
||||
constant bool PS_ABE [[function_constant(GSMTLConstantIndex_PS_ABE)]];
|
||||
constant uint PS_SW_ANISO [[function_constant(GSMTLConstantIndex_PS_SW_ANISO)]];
|
||||
constant bool PS_ROV_COLOR [[function_constant(GSMTLConstantIndex_PS_ROV_COLOR)]];
|
||||
constant uint PS_ROV_DEPTH_RAW [[function_constant(GSMTLConstantIndex_PS_ROV_DEPTH)]];
|
||||
|
||||
using GSShader::VSExpand;
|
||||
using AFAIL = GSShader::PS_AFAIL;
|
||||
using ATST = GSShader::PS_ATST;
|
||||
using GSShader::ZTST;
|
||||
using AA1 = GSShader::PS_AA1;
|
||||
using ROV_DEPTH = GSShader::PS_ROV_DEPTH;
|
||||
constant VSExpand VS_EXPAND_TYPE = static_cast<VSExpand>(VS_EXPAND_TYPE_RAW);
|
||||
constant AFAIL PS_AFAIL = static_cast<AFAIL>(PS_AFAIL_RAW);
|
||||
constant ATST PS_ATST = static_cast<ATST>(PS_ATST_RAW);
|
||||
constant ZTST PS_ZTST = static_cast<ZTST>(PS_ZTST_RAW);
|
||||
constant AA1 PS_AA1 = static_cast<AA1>(PS_AA1_RAW);
|
||||
constant ROV_DEPTH PS_ROV_DEPTH = static_cast<ROV_DEPTH>(PS_ROV_DEPTH_RAW);
|
||||
|
||||
#if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220
|
||||
#define PRIMID_SUPPORT 1
|
||||
@@ -119,9 +124,9 @@ constant bool NEEDS_DEPTH_FOR_ZTST = PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST
|
||||
constant bool NEEDS_DEPTH_FOR_AA1 = PS_AA1 == AA1::TRIANGLE_SW_Z;
|
||||
constant bool SW_DEPTH = NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1;
|
||||
|
||||
constant bool PS_COLOR0 = !PS_NO_COLOR;
|
||||
constant bool PS_COLOR1 = !PS_NO_COLOR1;
|
||||
constant bool PS_ZOUTPUT = PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH;
|
||||
constant bool PS_OUTPUT_COLOR0 = !PS_NO_COLOR && !PS_ROV_COLOR;
|
||||
constant bool PS_OUTPUT_COLOR1 = !PS_NO_COLOR1 && !PS_ROV_COLOR;
|
||||
constant bool PS_ZOUTPUT = (PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
||||
constant bool PS_ZOUTPUT_LESS = PS_ZOUTPUT && !SW_DEPTH;
|
||||
constant bool PS_ZOUTPUT_ANY = PS_ZOUTPUT && SW_DEPTH;
|
||||
constant bool PS_ZOUTPUT_COLOR = PS_ZOUTPUT_ANY && !DEPTH_FEEDBACK;
|
||||
@@ -165,21 +170,32 @@ struct MainPSIn
|
||||
uint interior [[function_constant(PS_INTERIOR)]];
|
||||
};
|
||||
|
||||
struct MainResult
|
||||
{
|
||||
float4 c0;
|
||||
float4 c1;
|
||||
float depth;
|
||||
};
|
||||
|
||||
struct MainPSOut
|
||||
{
|
||||
float4 c0 [[color(0), index(0), function_constant(PS_COLOR0)]];
|
||||
float4 c1 [[color(0), index(1), function_constant(PS_COLOR1)]];
|
||||
float4 c0 [[color(0), index(0), function_constant(PS_OUTPUT_COLOR0)]];
|
||||
float4 c1 [[color(0), index(1), function_constant(PS_OUTPUT_COLOR1)]];
|
||||
float depthColor [[color(1), function_constant(PS_ZOUTPUT_COLOR)]];
|
||||
float depthLess [[depth(less), function_constant(PS_ZOUTPUT_LESS)]];
|
||||
float depthAny [[depth(any), function_constant(PS_ZOUTPUT_ANY)]];
|
||||
void setDepth(float depth)
|
||||
MainPSOut(MainResult res)
|
||||
{
|
||||
if (PS_OUTPUT_COLOR0)
|
||||
c0 = res.c0;
|
||||
if (PS_OUTPUT_COLOR1)
|
||||
c1 = res.c1;
|
||||
if (PS_ZOUTPUT_LESS)
|
||||
depthLess = depth;
|
||||
depthLess = res.depth;
|
||||
if (PS_ZOUTPUT_ANY)
|
||||
depthAny = depth;
|
||||
depthAny = res.depth;
|
||||
if (PS_ZOUTPUT_COLOR)
|
||||
depthColor = depth;
|
||||
depthColor = res.depth;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -539,12 +555,6 @@ vertex MainVSOut vs_main_expand(
|
||||
out.inv_cov = is_near_corner ? 0.0f : 1.0f; // Full coverage at near corner, otherwise none.
|
||||
|
||||
out.interior = 0;
|
||||
|
||||
if (NOT_IIP)
|
||||
{
|
||||
// Get the provoking vertex color (first vertex in Metal)
|
||||
out.fc = i0 == 0 ? out.fc : (i1 == 0 ? other.fc : opposite.fc);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -564,11 +574,37 @@ struct PSMain
|
||||
float4 current_color;
|
||||
float current_depth;
|
||||
uint prim_id;
|
||||
bool color_discarded = false;
|
||||
bool depth_discarded = false;
|
||||
const thread MainPSIn& in;
|
||||
constant GSMTLMainPSUniform& cb;
|
||||
|
||||
PSMain(const thread MainPSIn& in, constant GSMTLMainPSUniform& cb): in(in), cb(cb) {}
|
||||
|
||||
void discard()
|
||||
{
|
||||
if (PS_ROV_COLOR || PS_ROV_DEPTH != ROV_DEPTH::NONE)
|
||||
color_discarded = depth_discarded = true;
|
||||
else
|
||||
discard_fragment();
|
||||
}
|
||||
|
||||
void discard_color(thread float4& output)
|
||||
{
|
||||
if (PS_ROV_COLOR)
|
||||
color_discarded = true;
|
||||
else
|
||||
output = current_color;
|
||||
}
|
||||
|
||||
void discard_depth(thread float& output)
|
||||
{
|
||||
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE)
|
||||
depth_discarded = true;
|
||||
else
|
||||
output = current_depth;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
float4 sample_tex(Args... args)
|
||||
{
|
||||
@@ -611,22 +647,27 @@ struct PSMain
|
||||
float4 sample_c_af(float2 uv, float uv_w)
|
||||
{
|
||||
// HW sampler will reject bad UVs, match that here.
|
||||
uv = any(isnan(uv) | isinf(uv)) ? float2(0, 0) : uv;
|
||||
uv = any(isnan(uv) | isinf(uv)) ? float2(0.0f, 0.0f) : uv;
|
||||
|
||||
// Large floating point values risk NaN/Inf values.
|
||||
// Above this value floats lose decimal precision, so seems a resonable limit for UVs.
|
||||
uv = clamp(uv, -8388608.0f, 8388608.0f);
|
||||
|
||||
// Below taken from https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#7.18.11%20LOD%20Calculations
|
||||
// And https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_filter_anisotropic.txt
|
||||
// With guidance from https://pema.dev/2025/05/09/mipmaps-too-much-detail/
|
||||
float2 sz = float2(get_tex_dims());
|
||||
float2 dX = dfdx(uv) * sz;
|
||||
float2 dY = dfdy(uv) * sz;
|
||||
|
||||
float length_x = length(dX);
|
||||
float length_y = length(dY);
|
||||
|
||||
// Calculate Ellipse Transform
|
||||
bool d_zero = length(dX) == 0 || length(dY) == 0;
|
||||
bool d_par = (dX.x * dY.y - dY.x * dX.y) == 0;
|
||||
bool d_per = dot(dX, dY) == 0;
|
||||
bool d_zero = length_x < 0.001f || length_y < 0.001f;
|
||||
float f = (dX.x * dY.y - dX.y * dY.x);
|
||||
bool d_par = f < 0.001f;
|
||||
bool d_per = dot(dX, dY) < 0.001f;
|
||||
bool d_inf_nan = any(isinf(dX) | isinf(dY) | isnan(dX) | isnan(dY));
|
||||
|
||||
if (!(d_zero || d_par || d_per || d_inf_nan))
|
||||
@@ -634,21 +675,30 @@ struct PSMain
|
||||
float A = dX.y * dX.y + dY.y * dY.y;
|
||||
float B = -2 * (dX.x * dX.y + dY.x * dY.y);
|
||||
float C = dX.x * dX.x + dY.x * dY.x;
|
||||
float f = (dX.x * dY.y - dY.x * dX.y);
|
||||
float F = f * f;
|
||||
|
||||
float p = A - C;
|
||||
float q = A + C;
|
||||
float t = sqrt(p * p + B * B);
|
||||
|
||||
float signB = sign(B);
|
||||
float denom_plus = t * (q + t);
|
||||
float denom_minus = t * (q - t);
|
||||
|
||||
float sqrtA = sqrt(F * (t + p));
|
||||
float sqrtB = sqrt(F * (t - p));
|
||||
|
||||
float inv_sqrt_denom_plus = rsqrt(denom_plus);
|
||||
float inv_sqrt_denom_minus = rsqrt(denom_minus);
|
||||
|
||||
float2 new_dX = float2(
|
||||
sqrt(F * (t + p) / (t * (q + t))),
|
||||
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
|
||||
sqrtA * inv_sqrt_denom_plus,
|
||||
sqrtB * inv_sqrt_denom_plus * signB
|
||||
);
|
||||
|
||||
float2 new_dY = float2(
|
||||
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
|
||||
sqrt(F * (t + p) / (t * (q - t)))
|
||||
sqrtB * inv_sqrt_denom_minus * -signB,
|
||||
sqrtA * inv_sqrt_denom_minus
|
||||
);
|
||||
|
||||
d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | isnan(new_dY));
|
||||
@@ -656,16 +706,15 @@ struct PSMain
|
||||
{
|
||||
dX = new_dX;
|
||||
dY = new_dY;
|
||||
length_x = length(dX);
|
||||
length_y = length(dY);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute AF values
|
||||
float squared_length_x = dX.x * dX.x + dX.y * dX.y;
|
||||
float squared_length_y = dY.x * dY.x + dY.y * dY.y;
|
||||
float determinant = abs(dX.x * dY.y - dX.y * dY.x);
|
||||
bool is_major_x = squared_length_x > squared_length_y;
|
||||
float squared_length_major = is_major_x ? squared_length_x : squared_length_y;
|
||||
float length_major = sqrt(squared_length_major);
|
||||
bool is_major_x = length_x > length_y;
|
||||
float length_major = is_major_x ? length_x : length_y;
|
||||
float length_minor = is_major_x ? length_y : length_x;
|
||||
|
||||
float aniso_ratio;
|
||||
float length_lod;
|
||||
@@ -677,41 +726,25 @@ struct PSMain
|
||||
// Perform isotropic filtering instead.
|
||||
aniso_ratio = 1.0f;
|
||||
length_lod = length_major;
|
||||
aniso_line = float2(0, 0);
|
||||
aniso_line = float2(0.0f, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
float norm_major = 1.0f / length_major;
|
||||
float2 aniso_line_dir = is_major_x ? dX : dY;
|
||||
|
||||
float2 aniso_line_dir = float2(
|
||||
(is_major_x ? dX.x : dY.x) * norm_major,
|
||||
(is_major_x ? dX.y : dY.y) * norm_major
|
||||
);
|
||||
|
||||
aniso_ratio = squared_length_major / determinant;
|
||||
|
||||
// Calculate the minor length of the ellipse for Lod, while also clamping the ratio of anisotropy.
|
||||
if (aniso_ratio > PS_SW_ANISO)
|
||||
{
|
||||
// ratio is clamped - Lod is based on ratio (preserves area)
|
||||
aniso_ratio = PS_SW_ANISO;
|
||||
length_lod = length_major / PS_SW_ANISO;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ratio not clamped - Lod is based on area
|
||||
length_lod = determinant / length_major;
|
||||
}
|
||||
aniso_ratio = min(length_major / length_minor, float(PS_SW_ANISO));
|
||||
length_lod = length_major / aniso_ratio;
|
||||
|
||||
// clamp to top Lod
|
||||
if (length_lod < 1.0f)
|
||||
aniso_ratio = max(1.0f, aniso_ratio * length_lod);
|
||||
|
||||
aniso_ratio = round(aniso_ratio);
|
||||
aniso_line = aniso_line_dir * 0.5f * length_major * (1.0f / sz);
|
||||
|
||||
aniso_line = aniso_line_dir * 0.5f * (1.0f / sz);
|
||||
}
|
||||
|
||||
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0;
|
||||
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0.0f;
|
||||
|
||||
float4 colour;
|
||||
if (aniso_ratio == 1.0f)
|
||||
@@ -720,10 +753,11 @@ struct PSMain
|
||||
}
|
||||
else
|
||||
{
|
||||
float4 num = float4(0, 0, 0, 0);
|
||||
float4 num = float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
float2 segment = (2.0f * aniso_line) / aniso_ratio;
|
||||
for (int i = 0; i < aniso_ratio; i++)
|
||||
{
|
||||
float2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
|
||||
float2 d = -aniso_line + (0.5f + i) * segment;
|
||||
float2 uv_sample = uv + d;
|
||||
float4 sample_colour = sample_tex(tex_sampler, uv_sample, level(lod));
|
||||
num += sample_colour;
|
||||
@@ -1456,9 +1490,9 @@ struct PSMain
|
||||
}
|
||||
}
|
||||
|
||||
MainPSOut ps_main()
|
||||
MainResult ps_main()
|
||||
{
|
||||
MainPSOut out = {};
|
||||
MainResult out = {};
|
||||
float input_z = in.p.z;
|
||||
if (PS_ZFLOOR)
|
||||
input_z = floor(input_z * 0x1p32) * 0x1p-32;
|
||||
@@ -1466,15 +1500,15 @@ struct PSMain
|
||||
if (PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER)
|
||||
{
|
||||
if (PS_ZTST == ZTST::GEQUAL && input_z < current_depth)
|
||||
discard_fragment();
|
||||
discard();
|
||||
if (PS_ZTST == ZTST::GREATER && input_z <= current_depth)
|
||||
discard_fragment();
|
||||
discard();
|
||||
}
|
||||
|
||||
if (PS_SCANMSK & 2)
|
||||
{
|
||||
if ((uint(in.p.y) & 1) == (PS_SCANMSK & 1))
|
||||
discard_fragment();
|
||||
discard();
|
||||
}
|
||||
|
||||
if (PS_DATE >= 5)
|
||||
@@ -1484,7 +1518,7 @@ struct PSMain
|
||||
bool bad = PS_RTA_CORRECTION ? ((PS_DATE & 3) == 1 ? (rt_a > (254.5f / 255.f)) : (rt_a < (254.5f / 255.f))) : ((PS_DATE & 3) == 1 ? (rt_a > 0.5) : (rt_a < 0.5));
|
||||
|
||||
if (bad)
|
||||
discard_fragment();
|
||||
discard();
|
||||
}
|
||||
|
||||
if (PS_DATE == 3)
|
||||
@@ -1493,7 +1527,7 @@ struct PSMain
|
||||
// Note prim_id == stencil_ceil will be the primitive that will update
|
||||
// the bad alpha value so we must keep it.
|
||||
if (float(prim_id) > stencil_ceil)
|
||||
discard_fragment();
|
||||
discard();
|
||||
}
|
||||
|
||||
float4 C = ps_color();
|
||||
@@ -1502,9 +1536,9 @@ struct PSMain
|
||||
|
||||
if (PS_AA1 != AA1::NONE)
|
||||
{
|
||||
float cov = PS_AA1 == AA1::LINE ?
|
||||
saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) : // Blur only outer part of the line by scaling coverage.
|
||||
saturate(1.f - abs(in.inv_cov));
|
||||
float cov = PS_AA1 == AA1::LINE
|
||||
? saturate(cb.line_cov_scale * (1.f - abs(in.inv_cov))) // Blur only outer part of the line by scaling coverage.
|
||||
: saturate(1.f - abs(in.inv_cov));
|
||||
if (!PS_ABE || floor(C.a) == 128.f) // The coverage is only used if the fragment alpha is 128.
|
||||
C.a = 128.f * cov;
|
||||
}
|
||||
@@ -1516,7 +1550,7 @@ struct PSMain
|
||||
|
||||
bool atst_pass = atst(C);
|
||||
if (PS_AFAIL == AFAIL::KEEP && !atst_pass)
|
||||
discard_fragment();
|
||||
discard();
|
||||
|
||||
float4 alpha_blend = float4(0.f);
|
||||
if (SW_AD_TO_HW)
|
||||
@@ -1622,32 +1656,32 @@ struct PSMain
|
||||
if (PS_AFAIL == AFAIL::RGB_ONLY_DSB)
|
||||
alpha_blend.a = float(atst_pass);
|
||||
|
||||
if (PS_COLOR0)
|
||||
if (!PS_NO_COLOR)
|
||||
{
|
||||
out.c0.a = PS_RTA_CORRECTION ? C.a / 128.f : C.a / 255.f;
|
||||
out.c0.rgb = PS_COLCLIP_HW ? float3(C.rgb / 65535.f) : C.rgb / 255.f;
|
||||
}
|
||||
if (PS_COLOR1)
|
||||
if (!PS_NO_COLOR1)
|
||||
out.c1 = alpha_blend;
|
||||
|
||||
if (PS_ZCLAMP)
|
||||
input_z = min(input_z, cb.max_depth);
|
||||
|
||||
if (PS_AA1 == AA1::TRIANGLE_SW_Z && !in.interior)
|
||||
input_z = current_depth; // No depth update for triangle edges.
|
||||
discard_depth(input_z); // No depth update for triangle edges.
|
||||
|
||||
if (!atst_pass)
|
||||
{
|
||||
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::RGB_ONLY)
|
||||
out.c0.a = current_color.a;
|
||||
out.c0.a = current_color.a; // discard alpha
|
||||
else if (PS_AFAIL == AFAIL::ZB_ONLY)
|
||||
out.c0 = current_color;
|
||||
discard_color(out.c0);
|
||||
|
||||
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::FB_ONLY)
|
||||
input_z = current_depth;
|
||||
discard_depth(input_z);
|
||||
}
|
||||
|
||||
out.setDepth(input_z);
|
||||
out.depth = input_z;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -1659,16 +1693,19 @@ fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]])
|
||||
return in * 2;
|
||||
}
|
||||
|
||||
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH;
|
||||
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH;
|
||||
constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK;
|
||||
constant bool NEEDS_RT_TEX = NEEDS_RT && !HAS_FBFETCH && !PS_ROV_COLOR;
|
||||
constant bool NEEDS_RT_FBF = NEEDS_RT && HAS_FBFETCH && !PS_ROV_COLOR;
|
||||
constant bool NEEDS_DS_FBF = SW_DEPTH && HAS_FBFETCH && !DEPTH_FEEDBACK && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
||||
#else
|
||||
constant bool NEEDS_RT_TEX = NEEDS_RT;
|
||||
constant bool NEEDS_RT_TEX = NEEDS_RT && !PS_ROV_COLOR;
|
||||
constant bool NEEDS_DS_FBF = false;
|
||||
constant float ds_fbf = 0;
|
||||
#endif
|
||||
constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF;
|
||||
constant bool NEEDS_DS_DEPTH = SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF;
|
||||
constant bool NEEDS_DS_TEX = SW_DEPTH && !DEPTH_FEEDBACK && !NEEDS_DS_FBF && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
||||
constant bool NEEDS_DS_DEPTH = (SW_DEPTH && DEPTH_FEEDBACK || NEEDS_DS_FBF) && PS_ROV_DEPTH == ROV_DEPTH::NONE;
|
||||
constant bool NEEDS_RT_ROV = PS_ROV_COLOR && !ROV_NEEDS_R32;
|
||||
constant bool NEEDS_RT_U32 = PS_ROV_COLOR && ROV_NEEDS_R32;
|
||||
constant bool NEEDS_DS_ROV = PS_ROV_DEPTH != ROV_DEPTH::NONE;
|
||||
|
||||
fragment MainPSOut ps_main(
|
||||
MainPSIn in [[stage_in]],
|
||||
@@ -1687,7 +1724,10 @@ fragment MainPSOut ps_main(
|
||||
texture2d<float> rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]],
|
||||
texture2d<float> primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]],
|
||||
texture2d<float> ds_tex [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_TEX)]],
|
||||
depth2d<float> ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]])
|
||||
depth2d<float> ds_depth [[texture(GSMTLTextureIndexDepthTarget), function_constant(NEEDS_DS_DEPTH)]],
|
||||
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
|
||||
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]],
|
||||
texture2d<float, access::read_write> ds_rov [[texture(GSMTLTextureIndexDepthTarget), raster_order_group(1), function_constant(NEEDS_DS_ROV)]])
|
||||
{
|
||||
PSMain main(in, cb);
|
||||
main.tex_sampler = s;
|
||||
@@ -1708,7 +1748,9 @@ fragment MainPSOut ps_main(
|
||||
|
||||
if (SW_DEPTH)
|
||||
{
|
||||
if (DEPTH_FEEDBACK)
|
||||
if (PS_ROV_DEPTH != ROV_DEPTH::NONE)
|
||||
main.current_depth = ds_rov.read(coord).x;
|
||||
else if (DEPTH_FEEDBACK)
|
||||
main.current_depth = ds_depth.read(coord);
|
||||
else if (NEEDS_DS_FBF)
|
||||
main.current_depth = ds_fbf < 0 ? ds_depth.read(coord) : ds_fbf;
|
||||
@@ -1716,20 +1758,80 @@ fragment MainPSOut ps_main(
|
||||
main.current_depth = ds_tex.read(coord).x;
|
||||
}
|
||||
|
||||
if (NEEDS_RT)
|
||||
if (NEEDS_RT || (PS_ROV_COLOR && any(cb.fbmask == 0xff)))
|
||||
{
|
||||
if (PS_ROV_COLOR)
|
||||
{
|
||||
if (ROV_NEEDS_R32)
|
||||
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
|
||||
else
|
||||
main.current_color = rt_rov.read(coord);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if FBFETCH_SUPPORT
|
||||
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
|
||||
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
|
||||
#else
|
||||
main.current_color = rt.read(coord);
|
||||
main.current_color = rt.read(coord);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
main.current_color = 0;
|
||||
}
|
||||
|
||||
return main.ps_main();
|
||||
MainResult out = main.ps_main();
|
||||
if (PS_ROV_DEPTH == ROV_DEPTH::READ_WRITE && !main.depth_discarded)
|
||||
ds_rov.write(out.depth, coord);
|
||||
if (PS_ROV_COLOR && !main.color_discarded)
|
||||
{
|
||||
if (!PS_FBMASK)
|
||||
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
|
||||
if (ROV_NEEDS_R32)
|
||||
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
|
||||
else
|
||||
rt_rov.write(out.c0, coord);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Metal doesn't let you toggle eft with function constants so we need a separate function for it
|
||||
[[early_fragment_tests]]
|
||||
fragment void ps_main_rov_eft(
|
||||
MainPSIn in [[stage_in]],
|
||||
constant GSMTLMainPSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
|
||||
sampler s [[sampler(0)]],
|
||||
texture2d<float> tex [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_COLOR)]],
|
||||
depth2d<float> depth [[texture(GSMTLTextureIndexTex), function_constant(PS_TEX_IS_DEPTH)]],
|
||||
texture2d<float> palette [[texture(GSMTLTextureIndexPalette), function_constant(PS_HAS_PALETTE)]],
|
||||
texture2d<float, access::read_write> rt_rov [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_ROV)]],
|
||||
texture2d<uint, access::read_write> rt_u32 [[texture(GSMTLTextureIndexRenderTarget), raster_order_group(0), function_constant(NEEDS_RT_U32)]])
|
||||
{
|
||||
PSMain main(in, cb);
|
||||
main.tex_sampler = s;
|
||||
if (PS_TEX_IS_COLOR)
|
||||
main.tex = tex;
|
||||
else
|
||||
main.tex_depth = depth;
|
||||
if (PS_HAS_PALETTE)
|
||||
main.palette = palette;
|
||||
|
||||
uint2 coord = uint2(in.p.xy);
|
||||
if (ROV_NEEDS_R32)
|
||||
main.current_color = unpack_unorm4x8_to_float(rt_u32.read(coord).x);
|
||||
else
|
||||
main.current_color = rt_rov.read(coord);
|
||||
MainPSOut out = main.ps_main();
|
||||
if (!main.color_discarded)
|
||||
{
|
||||
if (!PS_FBMASK)
|
||||
out.c0 = select(out.c0, main.current_color, cb.fbmask == 0xff);
|
||||
if (ROV_NEEDS_R32)
|
||||
rt_u32.write(pack_float_to_unorm4x8(out.c0), coord);
|
||||
else
|
||||
rt_rov.write(out.c0, coord);
|
||||
}
|
||||
}
|
||||
|
||||
#if PRIMID_SUPPORT
|
||||
|
||||
@@ -1262,7 +1262,7 @@ void GSDeviceOGL::DestroyPipelineStatisticsQueries()
|
||||
glEndQuery(GL_FRAGMENT_SHADER_INVOCATIONS_ARB);
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_pipeline_statistics_queries.size(); i++)
|
||||
for (size_t i = 0; i < m_pipeline_statistics_queries.size(); i++)
|
||||
{
|
||||
glDeleteQueries(2, m_pipeline_statistics_queries[i].data());
|
||||
m_pipeline_statistics_queries[i].fill(0);
|
||||
|
||||
@@ -1030,7 +1030,7 @@ void GameDatabase::initDatabase()
|
||||
const ryml::csubstr yaml = ryml::to_csubstr(*buffer);
|
||||
|
||||
Error error;
|
||||
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error);
|
||||
std::optional<ryml::Tree> tree = ParseYAMLFromString(yaml, ryml::to_csubstr(name), &error, true);
|
||||
if (!tree.has_value())
|
||||
{
|
||||
Console.ErrorFmt("GameDB: Failed to parse game database file {}:", path);
|
||||
|
||||
@@ -1298,9 +1298,11 @@ void FullscreenUI::DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size)
|
||||
#else
|
||||
localtime_r(&utc_time_t, &tm_local);
|
||||
#endif
|
||||
heading_str.format(FSUI_FSTR("{:%H:%M}"), tm_local);
|
||||
char buf[256];
|
||||
std::strftime(buf, sizeof(buf), "%X", &tm_local);
|
||||
heading_str.assign(buf);
|
||||
|
||||
const ImVec2 time_size = heading_font.first->CalcTextSizeA(heading_font.second, FLT_MAX, 0.0f, "00:00");
|
||||
const ImVec2 time_size = heading_font.first->CalcTextSizeA(heading_font.second, FLT_MAX, 0.0f, heading_str.c_str());
|
||||
time_pos = ImVec2(heading_size.x - LayoutScale(LAYOUT_MENU_BUTTON_X_PADDING) - time_size.x,
|
||||
LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING));
|
||||
ImGuiFullscreen::AddTextWithShadow(
|
||||
@@ -1563,20 +1565,10 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
|
||||
const float image_width = 60.0f;
|
||||
const float image_height = 90.0f;
|
||||
const std::string_view path_string(Path::GetFileName(s_current_disc_path));
|
||||
const ImVec2 title_size(
|
||||
g_large_font.first->CalcTextSizeA(g_large_font.second, std::numeric_limits<float>::max(), -1.0f, s_current_game_title.c_str()));
|
||||
const ImVec2 path_size(path_string.empty() ?
|
||||
ImVec2(0.0f, 0.0f) :
|
||||
g_medium_font.first->CalcTextSizeA(g_medium_font.second, std::numeric_limits<float>::max(), -1.0f,
|
||||
path_string.data(), path_string.data() + path_string.length()));
|
||||
const ImVec2 subtitle_size(g_medium_font.first->CalcTextSizeA(
|
||||
g_medium_font.second, std::numeric_limits<float>::max(), -1.0f, s_current_game_subtitle.c_str()));
|
||||
|
||||
ImVec2 title_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - title_size.x,
|
||||
display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height));
|
||||
ImVec2 path_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - path_size.x,
|
||||
title_pos.y + g_large_font.second + LayoutScale(4.0f));
|
||||
ImVec2 subtitle_pos(display_size.x - LayoutScale(10.0f + image_width + 20.0f) - subtitle_size.x,
|
||||
ImVec2 title_pos(LayoutScale(10.0f + image_width + 20.0f), LayoutScale(10.0f));
|
||||
ImVec2 path_pos(LayoutScale(10.0f + image_width + 20.0f), title_pos.y + g_large_font.second + LayoutScale(4.0f));
|
||||
ImVec2 subtitle_pos(LayoutScale(10.0f + image_width + 20.0f),
|
||||
(path_string.empty() ? title_pos.y + g_large_font.second : path_pos.y + g_medium_font.second) + LayoutScale(4.0f));
|
||||
|
||||
float rp_height = 0.0f;
|
||||
@@ -1593,12 +1585,8 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
|
||||
// Add a small extra gap if any Rich Presence is displayed
|
||||
rp_height = rp_size.y - g_medium_font.second + LayoutScale(2.0f);
|
||||
|
||||
const ImVec2 rp_pos(display_size.x - LayoutScale(20.0f + 50.0f + 20.0f) - rp_size.x,
|
||||
subtitle_pos.y + g_medium_font.second + LayoutScale(4.0f) - rp_height);
|
||||
|
||||
title_pos.y -= rp_height;
|
||||
path_pos.y -= rp_height;
|
||||
subtitle_pos.y -= rp_height;
|
||||
const ImVec2 rp_pos(LayoutScale(10.0f + image_width + 20.0f),
|
||||
subtitle_pos.y + g_medium_font.second + LayoutScale(4.0f));
|
||||
|
||||
DrawShadowedText(dl, g_medium_font, rp_pos, text_color, rp.data(), rp.data() + rp.length(), wrap_width);
|
||||
}
|
||||
@@ -1611,8 +1599,7 @@ void FullscreenUI::DrawPauseMenu(MainWindowType type)
|
||||
}
|
||||
DrawShadowedText(dl, g_medium_font, subtitle_pos, text_color, s_current_game_subtitle.c_str());
|
||||
|
||||
const ImVec2 image_min(display_size.x - LayoutScale(10.0f + image_width),
|
||||
display_size.y - LayoutScale(LAYOUT_FOOTER_HEIGHT) - LayoutScale(10.0f + image_height) - rp_height);
|
||||
const ImVec2 image_min(LayoutScale(10.0f), LayoutScale(10.0f));
|
||||
const ImVec2 image_max(image_min.x + LayoutScale(image_width), image_min.y + LayoutScale(image_height) + rp_height);
|
||||
{
|
||||
auto lock = GameList::GetLock();
|
||||
@@ -4080,7 +4067,6 @@ TRANSLATE_NOOP("FullscreenUI", "Downloading covers...");
|
||||
TRANSLATE_NOOP("FullscreenUI", "An error occurred while deleting empty game settings:\n{}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "An error occurred while saving game settings:\n{}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "{} is not a valid disc image.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "{:%H:%M}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This Session: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "All Time: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save Slot {0}");
|
||||
|
||||
@@ -5949,6 +5949,7 @@ TRANSLATE_NOOP("FullscreenUI", "Shows the host's CPU utilization based on thread
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU utilization.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows indicators when fast forwarding, pausing, and other abnormal states are active.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows debug information about the renderer.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the host's GPU pipeline statistics.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows a visual history of frame times.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the current configuration in the bottom-right corner of the display.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the amount of currently active patches/cheats on the bottom-right corner of the display.");
|
||||
@@ -6534,6 +6535,7 @@ TRANSLATE_NOOP("FullscreenUI", "Show CPU Usage");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show GPU Usage");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show Status Indicators");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show GPU Debug Info");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show GPU Stats");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show Frame Times");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show Patches");
|
||||
|
||||
+2
-2
@@ -607,12 +607,12 @@ bool IOPMemoryInterface::Write128(u32 address, u128 value)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IOPMemoryInterface::WriteBytes(u32 address, void* src, u32 size)
|
||||
bool IOPMemoryInterface::WriteBytes(u32 address, const void* src, u32 size)
|
||||
{
|
||||
return iopMemSafeWriteBytes(address, src, size);
|
||||
}
|
||||
|
||||
bool IOPMemoryInterface::CompareBytes(u32 address, void* src, u32 size)
|
||||
bool IOPMemoryInterface::CompareBytes(u32 address, const void* src, u32 size)
|
||||
{
|
||||
return iopMemSafeCmpBytes(address, src, size) == 0;
|
||||
}
|
||||
|
||||
+2
-2
@@ -136,7 +136,7 @@ public:
|
||||
bool Write32(u32 address, u32 value) override;
|
||||
bool Write64(u32 address, u64 value) override;
|
||||
bool Write128(u32 address, u128 value) override;
|
||||
bool WriteBytes(u32 address, void* src, u32 size) override;
|
||||
bool WriteBytes(u32 address, const void* src, u32 size) override;
|
||||
|
||||
bool CompareBytes(u32 address, void* src, u32 size) override;
|
||||
bool CompareBytes(u32 address, const void* src, u32 size) override;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user