mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-19 13:35:43 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ee1e1e22 | ||
|
|
2b02a5dc78 | ||
|
|
dead00eb62 | ||
|
|
4fa2b8e45d | ||
|
|
fc90973509 | ||
|
|
ff9176f83e | ||
|
|
63d3dd9978 | ||
|
|
b704c6caaa | ||
|
|
a64465681c | ||
|
|
539357436c | ||
|
|
22cb6b0c9c | ||
|
|
81df4e5def | ||
|
|
a3f3e8733d | ||
|
|
6fc84f76bb | ||
|
|
0949f193b8 | ||
|
|
5f3dc1a76e | ||
|
|
9afea55bb6 | ||
|
|
433614ae85 | ||
|
|
00c85c4a8f | ||
|
|
f9885315bd | ||
|
|
26563dbae2 | ||
|
|
53e3838c1d | ||
|
|
4509d11daf | ||
|
|
37d5bf33e8 | ||
|
|
76842401ee | ||
|
|
bf9d38e86a | ||
|
|
7de2aad05a | ||
|
|
7d11e1f963 | ||
|
|
452e239eb4 | ||
|
|
8b82fbb449 | ||
|
|
de84c41757 | ||
|
|
3000e113e2 | ||
|
|
c5d6b28be4 | ||
|
|
c0dfb8cfa0 | ||
|
|
f9feca4f4d | ||
|
|
4fa9c4e382 | ||
|
|
93f1fcbe88 | ||
|
|
3d71e310c0 | ||
|
|
02d0f613ba | ||
|
|
3091407e9e | ||
|
|
70f6169eff | ||
|
|
a1a3cd9d1b | ||
|
|
86c5d2ae2a | ||
|
|
d759055231 | ||
|
|
c6474852d5 | ||
|
|
a2ac576711 | ||
|
|
1b6924a033 | ||
|
|
8e5e053b81 | ||
|
|
6891511a1d | ||
|
|
c186f8d366 | ||
|
|
eed53d19a0 | ||
|
|
abb218c5cf | ||
|
|
249ac51827 | ||
|
|
3da22f126e | ||
|
|
a509350646 | ||
|
|
bd486f1729 | ||
|
|
f7ef43b155 | ||
|
|
282129030f | ||
|
|
5734403ffc | ||
|
|
8d4d9ce026 | ||
|
|
390e3994ad | ||
|
|
6c41c125d8 | ||
|
|
83da1b6479 | ||
|
|
226b4d25dc | ||
|
|
27736ef47f | ||
|
|
f95b2ce24d | ||
|
|
f1d83f8c65 | ||
|
|
84c0cd8890 | ||
|
|
971382f11e | ||
|
|
ddf6fe3264 | ||
|
|
e5f0c99c76 | ||
|
|
af81d8b7da | ||
|
|
8f23e31ae4 | ||
|
|
3737b1e06a | ||
|
|
de848fe468 | ||
|
|
ce2a859ad1 | ||
|
|
8cfd583605 | ||
|
|
8477679096 | ||
|
|
d0ee5b859a | ||
|
|
16f2c262a3 | ||
|
|
e8c750ee88 | ||
|
|
20bbb99516 | ||
|
|
d9e0cd55c7 | ||
|
|
9c64627b07 | ||
|
|
3cbc7f3f09 | ||
|
|
38bcdf65e3 | ||
|
|
2ae8f1f23f | ||
|
|
ed796ee3db | ||
|
|
b1962d22b0 | ||
|
|
3722d392d8 | ||
|
|
bda58fb0c1 | ||
|
|
6f1f4e81d0 | ||
|
|
928bf313df | ||
|
|
7b2fa803de | ||
|
|
2dbae3bec9 | ||
|
|
fa26937885 | ||
|
|
c610d4d2d4 | ||
|
|
2f9d7a2901 | ||
|
|
640a30a25b | ||
|
|
bf94ee1c8e | ||
|
|
1fa3c6aa4a | ||
|
|
76c401b0fc | ||
|
|
1249846e87 | ||
|
|
09fcc4d60e | ||
|
|
fa9c99a948 | ||
|
|
f85b20eb00 | ||
|
|
420f4177c9 | ||
|
|
4f5bde2cef | ||
|
|
601b0e60ea | ||
|
|
a2e354c629 | ||
|
|
241a929748 | ||
|
|
1bf80dbabd | ||
|
|
e6e9408f95 | ||
|
|
1c01f8486d | ||
|
|
8700ae00b5 | ||
|
|
427d53d3c4 | ||
|
|
b1a588cc50 | ||
|
|
19738064f6 | ||
|
|
0eb4441dac | ||
|
|
fee5ab8e99 | ||
|
|
6477a2d62e | ||
|
|
c26c2236c5 | ||
|
|
f3496e3858 | ||
|
|
177318b353 | ||
|
|
93695ce623 | ||
|
|
cc8292753f | ||
|
|
87319a2d2f | ||
|
|
bf5be8ed4f | ||
|
|
0fc1b66a9b | ||
|
|
cb63269d9e | ||
|
|
903e859433 |
@@ -27,7 +27,7 @@ LIBJPEGTURBO=3.1.4.1
|
||||
LIBPNG=1.6.56
|
||||
LIBWEBP=1.6.0
|
||||
NVENC=13.0.19.0
|
||||
SDL=SDL3-3.4.4
|
||||
SDL=SDL3-3.4.8
|
||||
LZ4=1.10.0
|
||||
VULKAN=1.4.328.1
|
||||
ZSTD=1.5.7
|
||||
@@ -60,7 +60,7 @@ b072aed6871998cce9b36e7774033105ca29e33632be5b6347f3206898e0756a ffmpeg-$FFMPEG
|
||||
ecae8008e2cc9ade2f2c1bb9d5e6d4fb73e7c433866a056bd82980741571a022 libjpeg-turbo-$LIBJPEGTURBO.tar.gz
|
||||
f7d8bf1601b7804f583a254ab343a6549ca6cf27d255c302c47af2d9d36a6f18 libpng-$LIBPNG.tar.xz
|
||||
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
|
||||
ee712dbe6a89bb140bbfc2ce72358fb5ee5cc2240abeabd54855012db30b3864 $SDL.tar.gz
|
||||
e9fff7467fb60f037e6708da18b25560649e4c63edc2a69bb871b960d9cbfbba $SDL.tar.gz
|
||||
9ce32d4a2763a2ac5f258726ba2f49e9011327c1ee8c30862a32d0f30889fbe8 libpng-$LIBPNG-apng.patch.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
13da39edb3a40ed9713ae390ca89faa2f1202c9dda869ef306a8d4383e242bee nv-codec-headers-$NVENC.tar.gz
|
||||
@@ -152,7 +152,7 @@ cd ..
|
||||
echo "Building libpng..."
|
||||
rm -fr "libpng-$LIBPNG"
|
||||
tar xf "libpng-$LIBPNG.tar.xz"
|
||||
gunzip -d -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
gzip -kd -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
cd "libpng-$LIBPNG"
|
||||
patch -p1 < "../libpng-$LIBPNG-apng.patch"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DBUILD_SHARED_LIBS=ON -DBUILD_SHARED_LIBS=ON -DPNG_TESTS=OFF -DPNG_STATIC=OFF -DPNG_SHARED=ON -DPNG_TOOLS=OFF -B build -G Ninja
|
||||
|
||||
@@ -87,7 +87,7 @@ cd ..
|
||||
echo "Building libpng..."
|
||||
rm -fr "libpng-$LIBPNG"
|
||||
tar xf "libpng-$LIBPNG.tar.xz"
|
||||
gunzip -d -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
gzip -kd -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
cd "libpng-$LIBPNG"
|
||||
patch -p1 < "../libpng-$LIBPNG-apng.patch"
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DBUILD_SHARED_LIBS=ON -DBUILD_SHARED_LIBS=ON -DPNG_TESTS=OFF -DPNG_STATIC=OFF -DPNG_SHARED=ON -DPNG_TOOLS=OFF -B build -G Ninja
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://libsdl.org/release/SDL3-3.4.4.tar.gz",
|
||||
"sha256": "ee712dbe6a89bb140bbfc2ce72358fb5ee5cc2240abeabd54855012db30b3864"
|
||||
"url": "https://libsdl.org/release/SDL3-3.4.8.tar.gz",
|
||||
"sha256": "e9fff7467fb60f037e6708da18b25560649e4c63edc2a69bb871b960d9cbfbba"
|
||||
}
|
||||
],
|
||||
"cleanup": [
|
||||
|
||||
@@ -47,7 +47,7 @@ QT=6.11.0
|
||||
QTAPNG=1.3.0
|
||||
|
||||
FREETYPE=2.14.3
|
||||
SDL=SDL3-3.4.4
|
||||
SDL=SDL3-3.4.8
|
||||
HARFBUZZ=14.0.0
|
||||
ZSTD=1.5.7
|
||||
LZ4=1.10.0
|
||||
@@ -93,7 +93,7 @@ cfb1993d7a10848965b01b9cf33a54b8a4ba4e5e3a6d28d59483e73f10d9fc76 qttools-everyw
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
|
||||
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
|
||||
ee712dbe6a89bb140bbfc2ce72358fb5ee5cc2240abeabd54855012db30b3864 $SDL.tar.gz
|
||||
e9fff7467fb60f037e6708da18b25560649e4c63edc2a69bb871b960d9cbfbba $SDL.tar.gz
|
||||
f29db9470e0ca5cef484e04e27baeec233aa428e8fdabe9e51b0f706c0809d24 harfbuzz-$HARFBUZZ.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
@@ -218,7 +218,7 @@ cd ..
|
||||
echo "Installing libpng..."
|
||||
rm -fr "libpng-$LIBPNG"
|
||||
tar xf "libpng-$LIBPNG.tar.xz"
|
||||
gunzip -d -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
gzip -kd -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
cd "libpng-$LIBPNG"
|
||||
patch -p1 < "../libpng-$LIBPNG-apng.patch"
|
||||
cmake "${CMAKE_COMMON[@]}" "$CMAKE_ARCH_X64" -DBUILD_SHARED_LIBS=ON -DPNG_TESTS=OFF -DPNG_FRAMEWORK=OFF -B build
|
||||
@@ -301,6 +301,8 @@ cd "qtbase-everywhere-src-$QT"
|
||||
|
||||
# Patch Qt to support macOS 11
|
||||
patch -p1 < "$SCRIPTDIR/qt-macos11compat.patch"
|
||||
# Backport fix build on Xcode 26.4 (https://codereview.qt-project.org/c/qt/qtbase/+/724619)
|
||||
patch -p1 < "$SCRIPTDIR/qt110-xcode264.patch"
|
||||
|
||||
# since we don't have a direct reference to QtSvg, it doesn't deployed directly from the main binary
|
||||
# (only indirectly from iconengines), and the libqsvg.dylib imageformat plugin does not get deployed.
|
||||
|
||||
@@ -24,7 +24,7 @@ QT=6.11.0
|
||||
QTAPNG=1.3.0
|
||||
|
||||
FREETYPE=2.14.3
|
||||
SDL=SDL3-3.4.4
|
||||
SDL=SDL3-3.4.8
|
||||
HARFBUZZ=14.0.0
|
||||
ZSTD=1.5.7
|
||||
LZ4=1.10.0
|
||||
@@ -69,7 +69,7 @@ cfb1993d7a10848965b01b9cf33a54b8a4ba4e5e3a6d28d59483e73f10d9fc76 qttools-everyw
|
||||
f1d3be3489f758efe1a8f12118a212febbe611aa670af32e0159fa3c1feab2a6 QtApng-$QTAPNG.tar.gz
|
||||
|
||||
36bc4f1cc413335368ee656c42afca65c5a3987e8768cc28cf11ba775e785a5f freetype-$FREETYPE.tar.xz
|
||||
ee712dbe6a89bb140bbfc2ce72358fb5ee5cc2240abeabd54855012db30b3864 $SDL.tar.gz
|
||||
e9fff7467fb60f037e6708da18b25560649e4c63edc2a69bb871b960d9cbfbba $SDL.tar.gz
|
||||
f29db9470e0ca5cef484e04e27baeec233aa428e8fdabe9e51b0f706c0809d24 harfbuzz-$HARFBUZZ.tar.gz
|
||||
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
|
||||
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
|
||||
@@ -170,7 +170,7 @@ cd ..
|
||||
echo "Installing libpng..."
|
||||
rm -fr "libpng-$LIBPNG"
|
||||
tar xf "libpng-$LIBPNG.tar.xz"
|
||||
gunzip -d -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
gzip -kd -f "libpng-$LIBPNG-apng.patch.gz"
|
||||
cd "libpng-$LIBPNG"
|
||||
patch -p1 < "../libpng-$LIBPNG-apng.patch"
|
||||
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DPNG_TESTS=OFF -DPNG_FRAMEWORK=OFF -B build
|
||||
@@ -244,6 +244,8 @@ cd "qtbase-everywhere-src-$QT"
|
||||
|
||||
# Patch Qt to support macOS 11
|
||||
patch -p1 < "$SCRIPTDIR/qt-macos11compat.patch"
|
||||
# Backport fix build on Xcode 26.4 (https://codereview.qt-project.org/c/qt/qtbase/+/724619)
|
||||
patch -p1 < "$SCRIPTDIR/qt110-xcode264.patch"
|
||||
|
||||
# since we don't have a direct reference to QtSvg, it doesn't deployed directly from the main binary
|
||||
# (only indirectly from iconengines), and the libqsvg.dylib imageformat plugin does not get deployed.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
diff --git a/src/corelib/thread/qyieldcpu.h b/src/corelib/thread/qyieldcpu.h
|
||||
index ab710ea..954c49a 100644
|
||||
--- a/src/corelib/thread/qyieldcpu.h
|
||||
+++ b/src/corelib/thread/qyieldcpu.h
|
||||
@@ -33,7 +33,9 @@
|
||||
noexcept
|
||||
#endif
|
||||
{
|
||||
-#if __has_builtin(__yield)
|
||||
+#if __has_builtin(__builtin_arm_yield)
|
||||
+ __builtin_arm_yield();
|
||||
+#elif __has_builtin(__yield)
|
||||
__yield(); // Generic
|
||||
#elif defined(_YIELD_PROCESSOR) && defined(Q_CC_MSVC)
|
||||
_YIELD_PROCESSOR(); // Generic; MSVC's <atomic>
|
||||
@@ -47,9 +49,6 @@
|
||||
_mm_pause();
|
||||
#elif defined(Q_PROCESSOR_X86)
|
||||
__asm__("pause"); // hopefully asm() works in this compiler
|
||||
-
|
||||
-#elif __has_builtin(__builtin_arm_yield)
|
||||
- __builtin_arm_yield();
|
||||
#elif defined(Q_PROCESSOR_ARM) && Q_PROCESSOR_ARM >= 7 && defined(Q_CC_GNU)
|
||||
__asm__("yield"); // this works everywhere
|
||||
|
||||
@@ -60,7 +60,7 @@ set QTAPNG=1.3.0
|
||||
|
||||
set FREETYPE=2.14.3
|
||||
set HARFBUZZ=14.0.0
|
||||
set SDL=SDL3-3.4.4
|
||||
set SDL=SDL3-3.4.8
|
||||
set LIBJPEGTURBO=3.1.4.1
|
||||
set LIBPNG=1656
|
||||
set LIBPNGLONG=1.6.56
|
||||
@@ -94,7 +94,7 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9ce32d4a2763a2ac5f258726ba2f49e9011327c1ee8c30862a32d0f30889fbe8 || 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 "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" 6bd4fbb665f77899a488b381c5b6e9681fc57c60b669738f985fea714f3456c5 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 506206e02f90c1f37e048eacaf9e8a3a7dc682fd27783eb0ff15a7d2dcc9c2af || 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
|
||||
|
||||
@@ -77,7 +77,7 @@ set LIBX264=b35605ace3ddf7c1a5d67a2eb553f034aef41d55
|
||||
|
||||
set FREETYPE=2.14.3
|
||||
set HARFBUZZ=14.0.0
|
||||
set SDL=SDL3-3.4.4
|
||||
set SDL=SDL3-3.4.8
|
||||
set LIBJPEGTURBO=3.1.4.1
|
||||
set LIBPNG=1656
|
||||
set LIBPNGLONG=1.6.56
|
||||
@@ -121,7 +121,7 @@ call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lp
|
||||
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9ce32d4a2763a2ac5f258726ba2f49e9011327c1ee8c30862a32d0f30889fbe8 || 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 "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" 6bd4fbb665f77899a488b381c5b6e9681fc57c60b669738f985fea714f3456c5 || goto error
|
||||
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 506206e02f90c1f37e048eacaf9e8a3a7dc682fd27783eb0ff15a7d2dcc9c2af || 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
|
||||
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
cmake --build build --config Release || exit /b
|
||||
cmake --install build --config Release || exit /b
|
||||
) else (
|
||||
msbuild "PCSX2_qt.sln" /m /v:m /p:Configuration="${{ inputs.configuration }}" /p:Platform="${{ inputs.platform }}"
|
||||
msbuild "PCSX2_qt.sln" /m /v:m /p:Configuration="${{ inputs.configuration }}" /p:Platform="${{ inputs.platform }}" || exit /b
|
||||
)
|
||||
REM We can use Segoe UI Emoji so we don't need to bundle this
|
||||
del bin\resources\fonts\Twemoji*
|
||||
|
||||
@@ -17558,7 +17558,7 @@ SLES-51257:
|
||||
name: "The Sims"
|
||||
name-sort: "Sims, The"
|
||||
region: "PAL-M8"
|
||||
compat: 3
|
||||
compat: 5
|
||||
speedHacks:
|
||||
mtvu: 0 # Fixes bad graphics due to bad T-Bit handling.
|
||||
SLES-51258:
|
||||
@@ -17719,7 +17719,7 @@ SLES-51315:
|
||||
compat: 5
|
||||
SLES-51316:
|
||||
name: "Grand Theft Auto - Vice City"
|
||||
region: "PAL-E"
|
||||
region: "PAL-A"
|
||||
SLES-51317:
|
||||
name: "Minority Report - Le Futur vous Rattrape"
|
||||
region: "PAL-F"
|
||||
@@ -20719,7 +20719,7 @@ SLES-52558:
|
||||
name: "Crimson Sea 2"
|
||||
region: "PAL-G"
|
||||
SLES-52559:
|
||||
name: "FIFA 2005"
|
||||
name: "FIFA Football 2005"
|
||||
region: "PAL-E"
|
||||
gameFixes:
|
||||
- GIFFIFOHack # Partially fixes graphical issues also needs EE cycle rate + 3.
|
||||
@@ -20728,9 +20728,10 @@ SLES-52559:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52560:
|
||||
name: "FIFA 2005"
|
||||
name: "FIFA Football 2005"
|
||||
region: "PAL-G"
|
||||
gameFixes:
|
||||
- GIFFIFOHack # Partially fixes graphical issues also needs EE cycle rate + 3.
|
||||
@@ -20739,9 +20740,10 @@ SLES-52560:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52561:
|
||||
name: "FIFA 2005"
|
||||
name: "FIFA Football 2005"
|
||||
region: "PAL-P-S"
|
||||
gameFixes:
|
||||
- GIFFIFOHack # Partially fixes graphical issues also needs EE cycle rate + 3.
|
||||
@@ -20750,9 +20752,10 @@ SLES-52561:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52562:
|
||||
name: "FIFA 2005"
|
||||
name: "FIFA Football 2005"
|
||||
region: "PAL-I"
|
||||
gameFixes:
|
||||
- GIFFIFOHack # Partially fixes graphical issues also needs EE cycle rate + 3.
|
||||
@@ -20761,7 +20764,8 @@ SLES-52562:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52563:
|
||||
name: "FIFA Football 2005"
|
||||
region: "PAL-M6"
|
||||
@@ -20772,7 +20776,11 @@ SLES-52563:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
memcardFilters: # Football Fusion with Total Club Manager 2005
|
||||
- "SLES-52674"
|
||||
- "SLES-52563"
|
||||
SLES-52567:
|
||||
name: "Catwoman"
|
||||
region: "PAL-M7"
|
||||
@@ -21167,10 +21175,13 @@ SLES-52673:
|
||||
name: "NHL 2005"
|
||||
region: "PAL-M5"
|
||||
SLES-52674:
|
||||
name: "Fussball Manager 2005"
|
||||
name: "Total Club Manager 2005"
|
||||
region: "PAL-M5"
|
||||
clampModes:
|
||||
vu1ClampMode: 3 # Fixes 3D rendering.
|
||||
memcardFilters: # Football Fusion with Total Club Manager 2005
|
||||
- "SLES-52674"
|
||||
- "SLES-52563"
|
||||
SLES-52675:
|
||||
name: "Hugo - Cannon Cruise"
|
||||
region: "PAL-Unk"
|
||||
@@ -21839,28 +21850,32 @@ SLES-52909:
|
||||
gameFixes:
|
||||
- EETimingHack # Fixes graphical corruption.
|
||||
gsHWFixes:
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52910:
|
||||
name: "UEFA Champions League 2005"
|
||||
name: "UEFA Champions League 2004-2005"
|
||||
region: "PAL-F-G"
|
||||
gameFixes:
|
||||
- EETimingHack # Fixes graphical corruption.
|
||||
gsHWFixes:
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52911:
|
||||
name: "UEFA Champions League 2005"
|
||||
name: "UEFA Champions League 2004-2005"
|
||||
region: "PAL-I-S"
|
||||
gameFixes:
|
||||
- EETimingHack # Fixes graphical corruption.
|
||||
gsHWFixes:
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52912:
|
||||
name: "UEFA Champions League 2005"
|
||||
name: "UEFA Champions League 2004-2005"
|
||||
region: "PAL-E-DU"
|
||||
gameFixes:
|
||||
- EETimingHack # Fixes graphical corruption.
|
||||
gsHWFixes:
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLES-52913:
|
||||
name: "Suikoden IV"
|
||||
region: "PAL-M5"
|
||||
@@ -27045,6 +27060,9 @@ SLES-54522:
|
||||
SLES-54527:
|
||||
name: "DreamWorks & Aardman Flushed Away"
|
||||
region: "PAL-M5"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 5 # Fixes post alignment.
|
||||
nativeScaling: 2 # Fixes post effects.
|
||||
SLES-54532:
|
||||
name: "Home Alone"
|
||||
region: "PAL-PL-P"
|
||||
@@ -28404,7 +28422,7 @@ SLES-54960:
|
||||
name: "Godzilla - Unleashed"
|
||||
region: "PAL-M5"
|
||||
SLES-54961:
|
||||
name: "World series of poker 2008"
|
||||
name: "World Series of Poker 2008 - Battle for the Bracelets"
|
||||
region: "PAL-E"
|
||||
SLES-54962:
|
||||
name: "Guitar Hero III - Legends of Rock"
|
||||
@@ -31951,6 +31969,9 @@ SLKA-25176:
|
||||
name: "펌프 잇 업 익시드"
|
||||
name-en: "Pump It Up - Exceed"
|
||||
region: "NTSC-K"
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 4 # Fixes graphical corruption in musical notes.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLKA-25180:
|
||||
name: "스플린터 셀 - 판도라 투머로우"
|
||||
name-en: "Tom Clancy's Splinter Cell - Pandora Tomorrow"
|
||||
@@ -32125,7 +32146,8 @@ SLKA-25209:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLKA-25210:
|
||||
name: "서머 히트 비치 발리볼" # Undumped on ReDump as of 2025-08-28
|
||||
name-en: "Summer Heat Beach Volleyball"
|
||||
@@ -44020,7 +44042,7 @@ SLPM-65495:
|
||||
SLPM-65496:
|
||||
name: "ハイパーストリートファイターⅡ アニバーサリーエディション"
|
||||
name-sort: "はいぱーすとりーとふぁいたー2 あにばーさりーえでぃしょん"
|
||||
name-en: "Hyper Street Fighter"
|
||||
name-en: "Hyper Street Fighter II - The Anniversary Edition"
|
||||
region: "NTSC-J"
|
||||
SLPM-65497:
|
||||
name: "ワールドサッカーウイニングイレブン7 インターナショナル プレミアムパッケージ supported by アディダス"
|
||||
@@ -54487,6 +54509,7 @@ SLPM-74405:
|
||||
region: "NTSC-J"
|
||||
gameFixes:
|
||||
- EETimingHack # Stops crash after intro movie.
|
||||
- InstantDMAHack # Stops hang when entering Sword Select screen.
|
||||
SLPM-74406:
|
||||
name: "WRC ~WORLD RALLY CHAMPIONSHIP~ [PlayStation2 the Best]"
|
||||
name-sort: "WRC ~わーるど らりー ちゃんぴおんしっぷ~ [PlayStation2 the Best]"
|
||||
@@ -69652,7 +69675,7 @@ SLUS-21050:
|
||||
getSkipCount: "GSC_BurnoutGames"
|
||||
beforeDraw: "OI_BurnoutGames"
|
||||
SLUS-21051:
|
||||
name: "FIFA 2005"
|
||||
name: "FIFA Soccer 2005"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gameFixes:
|
||||
@@ -69662,7 +69685,8 @@ SLUS-21051:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLUS-21052:
|
||||
name: "Disney/Pixar Monsters, Inc."
|
||||
region: "NTSC-U"
|
||||
@@ -70095,6 +70119,9 @@ SLUS-21131:
|
||||
name: "Pump It Up - Exceed"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
cpuSpriteRenderBW: 4 # Fixes graphical corruption in musical notes.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLUS-21132:
|
||||
name: "Stella Deus - The Gate of Eternity"
|
||||
region: "NTSC-U"
|
||||
@@ -72499,6 +72526,9 @@ SLUS-21484:
|
||||
name: "DreamWorks & Aardman Flushed Away"
|
||||
region: "NTSC-U"
|
||||
compat: 5
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 5 # Fixes post alignment.
|
||||
nativeScaling: 2 # Fixes post effects.
|
||||
SLUS-21485:
|
||||
name: "Backyard Sports - Basketball 2007"
|
||||
region: "NTSC-U"
|
||||
@@ -75569,7 +75599,8 @@ SLUS-29127:
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 2 # Fixes misaligned bloom and edge garbage.
|
||||
nativeScaling: 1 # Fixes misaligned bloom.
|
||||
mipmap: 0 # Currently totally breaks player uniform rendering if mipmapping is enabled.
|
||||
cpuSpriteRenderBW: 2 # Fixes player outfit rendering.
|
||||
cpuSpriteRenderLevel: 2 # Needed for above.
|
||||
SLUS-29129:
|
||||
name: "25 to Life [Public Beta Vol.1.0]"
|
||||
region: "NTSC-U"
|
||||
@@ -75930,6 +75961,9 @@ SLUS-29196:
|
||||
SLUS-29197:
|
||||
name: "DreamWorks & Aardman Flushed Away [Demo]"
|
||||
region: "NTSC-U"
|
||||
gsHWFixes:
|
||||
halfPixelOffset: 5 # Fixes post alignment.
|
||||
nativeScaling: 2 # Fixes post effects.
|
||||
SLUS-29198:
|
||||
name: "Guitar Hero II [Demo]"
|
||||
region: "NTSC-U"
|
||||
|
||||
+367
-104
File diff suppressed because it is too large
Load Diff
@@ -654,6 +654,7 @@
|
||||
030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000830500000052000000000000,Rockfire CosmoVoyager,a:b0,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,guide:b10,leftshoulder:b6,lefttrigger:b7,misc1:b11,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
|
||||
03000000830500007030000000000000,Rockfire Space Ranger,a:b0,b:b1,back:b5,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b9,righttrigger:b8,start:b2,x:b3,y:b4,platform:Windows,
|
||||
03000000050b0000e318000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
|
||||
03000000050b0000e518000000000000,ROG Chakram,a:b1,b:b0,leftx:a0,lefty:a1,x:b2,y:b3,platform:Windows,
|
||||
@@ -682,6 +683,7 @@
|
||||
03000000a306000020f6000000000000,Saitek PS2700 PlayStation Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,
|
||||
03000000300f00001101000000000000,Saitek Rumble,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,
|
||||
03000000e804000000a0000000000000,Samsung EIGP20,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
|
||||
03000000bd12000010d1000000000000,Sanwa 4Button,a:b0,b:b1,x:b2,y:b3,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,platform:Windows,
|
||||
03000000c01100000252000000000000,Sanwa Easy Grip,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Windows,
|
||||
03000000c01100004350000000000000,Sanwa Micro Grip P3,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,x:b3,y:b2,platform:Windows,
|
||||
03000000411200004550000000000000,Sanwa Micro Grip Pro,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a1,righty:a2,start:b9,x:b1,y:b3,platform:Windows,
|
||||
@@ -773,6 +775,7 @@
|
||||
03000000d60600002a00000000000000,Trust 850F,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,x:b3,y:b4,platform:Windows,
|
||||
030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000005f1400002a01000000000000,Trust Predator GM1200,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,x:b3,y:b4,platform:Windows,
|
||||
030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows,
|
||||
03000000411200000450000000000000,Twin Shock,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a4,start:b11,x:b3,y:b0,platform:Windows,
|
||||
03000000d90400000200000000000000,TwinShock PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,
|
||||
@@ -1008,6 +1011,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000006d04000018c2000000000000,Logitech F510,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
|
||||
030000006d0400001ac2000004000000,Logitech Precision,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
|
||||
@@ -1334,7 +1338,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000b40400001124000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
03000000b40400001224000011010000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b2,paddle1:b16,paddle2:b17,paddle3:b14,paddle4:b15,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
05000000151900004000000001000000,Flydigi Vader 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b12,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b14,paddle1:b2,paddle2:b5,paddle3:b16,paddle4:b17,rightshoulder:b7,rightstick:b13,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
06000000d73700000124000000000000,Flydigi Vader 5 Pro,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,misc1:b11,misc2:b12,misc3:b13,misc4:b14,paddle1:b15,paddle2:b17,paddle3:b16,paddle4:b18,misc5:b19,platform:Linux,
|
||||
06000000d73700000124000000000000,Flydigi Vader 5 Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,misc2:b12,misc3:b13,misc4:b14,misc5:b19,paddle1:b15,paddle2:b17,paddle3:b16,paddle4:b18,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000007e0500003703000000000000,GameCube Adapter,a:b0,b:b1,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b2,platform:Linux,
|
||||
19000000030000000300000002030000,GameForce Controller,a:b1,b:b0,back:b8,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b16,leftshoulder:b4,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b5,rightstick:b15,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,
|
||||
03000000373500000b10000019010000,GameSir Cyclone 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
@@ -1440,6 +1444,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000bd12000003c0000010010000,Joypad Alpha Shock,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
03000000242f00002d00000011010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000242f00008a00000011010000,JYS Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,
|
||||
03000000314100001935000000010000,LeadJoy Xeno Plus,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:+a5,rightx:a4,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
030000006d040000d1ca000000000000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000006d040000d1ca000011010000,Logitech Chillstream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
@@ -1533,6 +1538,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
05000000853200000503000000010000,Nacon MGX 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,
|
||||
0300000085320000170d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
|
||||
0300000085320000190d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
|
||||
0300000085320000180d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
|
||||
0300000085320000160d000011010000,Nacon Revolution 5 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux,
|
||||
030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
|
||||
030000004f1f00000800000011010000,NeoGeo PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,
|
||||
0300000092120000474e000000010000,NeoGeo X Arcade Stick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b3,y:b2,platform:Linux,
|
||||
@@ -1790,6 +1797,8 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
03000000f00600000300000003000000,TRBot Virtual Joypad,a:b11,b:b12,back:b15,dpdown:b6,dpleft:b3,dpright:b4,dpup:b5,leftshoulder:b17,leftstick:b21,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b22,righttrigger:a2,rightx:a3,righty:a4,start:b16,x:b13,y:b14,platform:Linux,
|
||||
030000005f140000c501000010010000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
|
||||
06000000f51000000870000003010000,Turtle Beach Recon,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
03000000f51000001370000000010000,Turtle Beach ReactR,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,
|
||||
03000000f51000001370000000000000,Turtle Beach ReactR,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,
|
||||
03000000100800000100000010010000,Twin PlayStation Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,
|
||||
03000000c0160000e105000010010000,Ultimate Atari Fight Stick,a:b1,b:b2,back:b9,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,rightshoulder:b4,righttrigger:b5,start:b8,x:b0,y:b3,platform:Linux,
|
||||
03000000151900005678000010010000,Uniplay U6,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
|
||||
@@ -43,6 +43,13 @@
|
||||
#define PS_ATST_NOTEQUAL 4
|
||||
#endif
|
||||
|
||||
#ifndef PS_AA1_NONE
|
||||
#define PS_AA1_NONE 0
|
||||
#define PS_AA1_LINE 1
|
||||
#define PS_AA1_TRIANGLE 2
|
||||
#define PS_AA1_TRIANGLE_SW_Z 3
|
||||
#endif
|
||||
|
||||
#ifndef PS_FST
|
||||
#define PS_IIP 0
|
||||
#define PS_FST 0
|
||||
@@ -102,6 +109,17 @@
|
||||
#define PS_NO_COLOR1 0
|
||||
#define PS_DATE 0
|
||||
#define PS_TEX_IS_FB 0
|
||||
#define PS_AA1 0
|
||||
#define PS_ABE 0
|
||||
#endif
|
||||
|
||||
#ifndef VS_EXPAND_NONE
|
||||
#define VS_EXPAND_NONE 0
|
||||
#define VS_EXPAND_POINT 1
|
||||
#define VS_EXPAND_LINE 2
|
||||
#define VS_EXPAND_SPRITE 3
|
||||
#define VS_EXPAND_LINE_AA1 4
|
||||
#define VS_EXPAND_TRIANGLE_AA1 5
|
||||
#endif
|
||||
|
||||
#define SW_BLEND (PS_BLEND_A || PS_BLEND_B || PS_BLEND_D)
|
||||
@@ -110,7 +128,8 @@
|
||||
#define NEEDS_RT_FOR_AFAIL (PS_AFAIL == AFAIL_ZB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define NEEDS_DEPTH_FOR_AFAIL (PS_AFAIL == AFAIL_FB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define NEEDS_DEPTH_FOR_ZTST (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER)
|
||||
#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST)
|
||||
#define NEEDS_DEPTH_FOR_AA1 (PS_AA1 == PS_AA1_TRIANGLE_SW_Z)
|
||||
#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1)
|
||||
#define ZWRITE (PS_ZFLOOR || PS_ZCLAMP || SW_DEPTH)
|
||||
|
||||
struct VS_INPUT
|
||||
@@ -135,6 +154,9 @@ struct VS_OUTPUT
|
||||
#else
|
||||
nointerpolation float4 c : COLOR0;
|
||||
#endif
|
||||
|
||||
float inv_cov : COLOR1; // We use the inverse to make it simpler to interpolate.
|
||||
nointerpolation uint interior : COLOR2; // 1 for triangle interior; 0 for edge;
|
||||
};
|
||||
|
||||
struct PS_INPUT
|
||||
@@ -147,6 +169,8 @@ struct PS_INPUT
|
||||
#else
|
||||
nointerpolation float4 c : COLOR0;
|
||||
#endif
|
||||
float inv_cov : COLOR1; // We use the inverse to make it simpler to interpolate.
|
||||
nointerpolation uint interior : COLOR2; // 1 for triangle interior; 0 for edge;
|
||||
#if (PS_DATE >= 1 && PS_DATE <= 3) || GS_FORWARD_PRIMID
|
||||
uint primid : SV_PrimitiveID;
|
||||
#endif
|
||||
@@ -220,6 +244,155 @@ cbuffer cb1
|
||||
float RcpScaleFactor;
|
||||
};
|
||||
|
||||
#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1)
|
||||
float manual_lod(float uv_w)
|
||||
{
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(uv_w)) * L;
|
||||
// FIXME max useful ?
|
||||
//return max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
return min(gs_lod, max_lod) - bias;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// 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_inf_nan = any(isinf(dX) | isinf(dY) | isnan(dX) | isnan(dY));
|
||||
|
||||
if (!(d_zero || d_par || d_per || d_inf_nan))
|
||||
{
|
||||
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);
|
||||
|
||||
float2 new_dX = float2(
|
||||
sqrt(F * (t + p) / (t * (q + t))),
|
||||
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
|
||||
);
|
||||
|
||||
float2 new_dY = float2(
|
||||
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
|
||||
sqrt(F * (t + p) / (t * (q - t)))
|
||||
);
|
||||
|
||||
d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | isnan(new_dY));
|
||||
if (!d_inf_nan)
|
||||
{
|
||||
dX = new_dX;
|
||||
dY = new_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);
|
||||
|
||||
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.
|
||||
// A small length_major would result in aniso_ratio getting clamped to 1.
|
||||
// Perform isotropic filtering instead.
|
||||
aniso_ratio = 1.0f;
|
||||
length_lod = length_major;
|
||||
aniso_line = float2(0, 0);
|
||||
}
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#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
|
||||
#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;
|
||||
float2 uv_sample = uv + d;
|
||||
float4 sample_colour = Texture.SampleLevel(TextureSampler, uv_sample, lod);
|
||||
num += sample_colour;
|
||||
}
|
||||
|
||||
colour = num / aniso_ratio;
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
#endif
|
||||
|
||||
float4 sample_c(float2 uv, float uv_w, int2 xy)
|
||||
{
|
||||
#if PS_TEX_IS_FB == 1
|
||||
@@ -252,21 +425,12 @@ float4 sample_c(float2 uv, float uv_w, int2 xy)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PS_AUTOMATIC_LOD == 1
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
return sample_c_af(uv, uv_w);
|
||||
#elif PS_AUTOMATIC_LOD == 1
|
||||
return Texture.Sample(TextureSampler, uv);
|
||||
#elif PS_MANUAL_LOD == 1
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(uv_w)) * L;
|
||||
// FIXME max useful ?
|
||||
//float lod = max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
float lod = min(gs_lod, max_lod) - bias;
|
||||
|
||||
return Texture.SampleLevel(TextureSampler, uv, lod);
|
||||
return Texture.SampleLevel(TextureSampler, uv, manual_lod(uv_w));
|
||||
#else
|
||||
return Texture.SampleLevel(TextureSampler, uv, 0); // No lod
|
||||
#endif
|
||||
@@ -1077,7 +1241,15 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
#endif
|
||||
float4 C = ps_color(input);
|
||||
|
||||
#if PS_FIXED_ONE_A
|
||||
#if PS_AA1
|
||||
float cov = clamp(1.0f - abs(input.inv_cov), 0.0f, 1.0f);
|
||||
#if PS_ABE
|
||||
if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128.
|
||||
C.a = 128.0f * cov;
|
||||
#else
|
||||
C.a = 128.0f * cov;
|
||||
#endif
|
||||
#elif PS_FIXED_ONE_A
|
||||
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
||||
C.a = 128.0f;
|
||||
#endif
|
||||
@@ -1210,10 +1382,7 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
uint4 denorm_c = uint4(C);
|
||||
uint2 denorm_TA = uint2(float2(TA.xy) * 255.0f + 0.5f);
|
||||
C.rb = (float2)float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5));
|
||||
if (denorm_c.a & 0x80u)
|
||||
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u));
|
||||
else
|
||||
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u));
|
||||
C.ga = (float2)float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u));
|
||||
}
|
||||
else if (PS_SHUFFLE_ACROSS)
|
||||
{
|
||||
@@ -1279,6 +1448,11 @@ PS_OUTPUT ps_main(PS_INPUT input)
|
||||
input.p.z = min(input.p.z, MaxDepthPS);
|
||||
#endif
|
||||
|
||||
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
|
||||
if (!bool(input.interior))
|
||||
input.p.z = DepthTexture.Load(int3(input.p.xy, 0)).r; // No depth update for triangle edges.
|
||||
#endif
|
||||
|
||||
#if ZWRITE
|
||||
#if SW_DEPTH && PS_NO_COLOR1 && DX12
|
||||
// Output color clone for feedback as well as real depth.
|
||||
@@ -1310,7 +1484,19 @@ cbuffer cb0
|
||||
float2 TextureOffset;
|
||||
float2 PointSize;
|
||||
uint MaxDepth;
|
||||
uint BaseVertex; // Only used in DX11.
|
||||
uint _cb0_pad0;
|
||||
};
|
||||
|
||||
#ifdef DX12
|
||||
cbuffer cb2 : register(b2)
|
||||
#else
|
||||
cbuffer cb2
|
||||
#endif
|
||||
{
|
||||
uint BaseVertex;
|
||||
uint BaseIndex;
|
||||
uint _cb2_pad0;
|
||||
uint _cb2_pad1;
|
||||
};
|
||||
|
||||
VS_OUTPUT vs_main(VS_INPUT input)
|
||||
@@ -1362,10 +1548,14 @@ VS_OUTPUT vs_main(VS_INPUT input)
|
||||
output.c = input.c;
|
||||
output.t.z = input.f.r;
|
||||
|
||||
// Silence compiler warnings; should be optimized out when not needed.
|
||||
output.inv_cov = 0.0f;
|
||||
output.interior = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#if VS_EXPAND != 0
|
||||
#if VS_EXPAND != VS_EXPAND_NONE
|
||||
|
||||
struct VS_RAW_INPUT
|
||||
{
|
||||
@@ -1379,14 +1569,19 @@ struct VS_RAW_INPUT
|
||||
};
|
||||
|
||||
StructuredBuffer<VS_RAW_INPUT> vertices : register(t0);
|
||||
StructuredBuffer<uint> IndexBuffer : register(t5);
|
||||
|
||||
uint load_index(uint _i)
|
||||
{
|
||||
uint i = _i + BaseIndex;
|
||||
// i is even => load lower 16 bits; i odd => load upper 16 bits.
|
||||
uint shift = (i & 1u) << 4u;
|
||||
return (IndexBuffer.Load(i >> 1u) >> shift) & 0xFFFFu;
|
||||
}
|
||||
|
||||
VS_INPUT load_vertex(uint index)
|
||||
{
|
||||
#ifdef DX12
|
||||
VS_RAW_INPUT raw = vertices.Load(index);
|
||||
#else
|
||||
VS_RAW_INPUT raw = vertices.Load(BaseVertex + index);
|
||||
#endif
|
||||
|
||||
VS_INPUT vert;
|
||||
vert.st = raw.ST;
|
||||
@@ -1401,7 +1596,7 @@ VS_INPUT load_vertex(uint index)
|
||||
|
||||
VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
|
||||
{
|
||||
#if VS_EXPAND == 1 // Point
|
||||
#if VS_EXPAND == VS_EXPAND_POINT
|
||||
|
||||
VS_OUTPUT vtx = vs_main(load_vertex(vid >> 2));
|
||||
|
||||
@@ -1410,7 +1605,13 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
|
||||
|
||||
return vtx;
|
||||
|
||||
#elif VS_EXPAND == 2 // Line
|
||||
#elif (VS_EXPAND == VS_EXPAND_LINE) || (VS_EXPAND == VS_EXPAND_LINE_AA1)
|
||||
|
||||
// The difference between EXPAND_LINE and EXPAND_LINE_AA1
|
||||
// is that EXPAND_LINE expands in the perpendicular direction while
|
||||
// EXPAND_LINE_AA1 expands in the Y direction for shallow lines (X dominant)
|
||||
// and the X direction for steep lines (Y dominant).
|
||||
// EXPAND_LINE_AA1 also adds coverage to the output.
|
||||
|
||||
uint vid_base = vid >> 2;
|
||||
bool is_bottom = vid & 2;
|
||||
@@ -1419,20 +1620,31 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
|
||||
VS_OUTPUT vtx = vs_main(load_vertex(vid_base));
|
||||
VS_OUTPUT other = vs_main(load_vertex(vid_other));
|
||||
|
||||
float2 line_vector = normalize(vtx.p.xy - other.p.xy);
|
||||
float2 line_normal = float2(line_vector.y, -line_vector.x);
|
||||
float2 line_width = (line_normal * PointSize) / 2;
|
||||
// line_normal is inverted for bottom point
|
||||
float2 offset = (is_bottom ^ is_right) ? line_width : -line_width;
|
||||
// Use bottom minus top for delta regardless of which vertex we are expanding.
|
||||
float2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
|
||||
float2 line_vector = normalize(line_delta);
|
||||
#if VS_EXPAND == VS_EXPAND_LINE
|
||||
float2 line_expand = float2(line_vector.y, -line_vector.x);
|
||||
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
// Expand in y direction for shallow lines and x direction for steep lines.
|
||||
line_delta /= VertexScale;
|
||||
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 2.0f) : float2(2.0f, 0.0f);
|
||||
#endif
|
||||
float2 line_width = (line_expand * PointSize) / 2;
|
||||
float2 offset = is_right ? line_width : -line_width;
|
||||
vtx.p.xy += offset;
|
||||
|
||||
#if VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
vtx.inv_cov = is_right ? 1.0f : -1.0f;
|
||||
#endif
|
||||
|
||||
// Lines will be run as (0 1 2) (1 2 3)
|
||||
// This means that both triangles will have a point based off the top line point as their first point
|
||||
// So we don't have to do anything for !IIP
|
||||
|
||||
return vtx;
|
||||
|
||||
#elif VS_EXPAND == 3 // Sprite
|
||||
#elif VS_EXPAND == VS_EXPAND_SPRITE
|
||||
|
||||
// Sprite points are always in pairs
|
||||
uint vid_base = vid >> 1;
|
||||
@@ -1455,6 +1667,72 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID)
|
||||
|
||||
return vtx;
|
||||
|
||||
#elif VS_EXPAND == VS_EXPAND_TRIANGLE_AA1
|
||||
|
||||
// Triangles with AA1 are expanded as follows:
|
||||
// - Vertices 0-2: Interior of triangle (1 triangle).
|
||||
// - Vertices 3-8: First edge expanded (2 triangles).
|
||||
// - Vertices 9-14: Second edge expanded (2 triangles).
|
||||
// - Vertices 15-20: Third edge expanded (2 triangles).
|
||||
|
||||
uint prim_id = vid / 21;
|
||||
uint prim_offset = vid - 21 * prim_id; // range: 0-20
|
||||
bool interior = prim_offset < 3;
|
||||
|
||||
VS_OUTPUT vtx;
|
||||
if (interior)
|
||||
{
|
||||
vtx = vs_main(load_vertex(load_index(3 * prim_id + prim_offset)));
|
||||
vtx.inv_cov = 0.0f; // Full coverage
|
||||
vtx.interior = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
|
||||
uint prim_offset_edges = prim_offset - 3; // range: 0-17
|
||||
uint i0 = prim_offset_edges / 6;
|
||||
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
||||
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
||||
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
|
||||
|
||||
// Note: order of top/bottom, inside/outside order is arbitrary,
|
||||
// as long as it assembles into two triangles forming a quad.
|
||||
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
|
||||
bool is_outside = edge_offset & 1;
|
||||
|
||||
vtx = vs_main(load_vertex(load_index(3 * prim_id + i0)));
|
||||
VS_OUTPUT other = vs_main(load_vertex(load_index(3 * prim_id + i1)));
|
||||
VS_OUTPUT opposite = vs_main(load_vertex(load_index(3 * prim_id + i2)));
|
||||
|
||||
// Similar expansion to line AA1 except instead of expanding on both sides of
|
||||
// the line we expand on on the side towards the outside of the triangle.
|
||||
float2 line_delta = vtx.p.xy - other.p.xy;
|
||||
float2 line_normal = normalize(float2(line_delta.y, -line_delta.x));
|
||||
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0.0f, 2.0f) : float2(2.0f, 0.0f);
|
||||
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
|
||||
{
|
||||
// Expand direction point towards the interior so flip it.
|
||||
line_expand = -line_expand;
|
||||
}
|
||||
float2 line_width = (line_expand * PointSize) / 2;
|
||||
|
||||
if (is_bottom)
|
||||
vtx = other;
|
||||
if (is_outside)
|
||||
{
|
||||
vtx.p.xy += line_width;
|
||||
vtx.inv_cov = 1.0f; // No coverage
|
||||
}
|
||||
else
|
||||
{
|
||||
vtx.inv_cov = 0.0f; // Full coverage
|
||||
}
|
||||
|
||||
vtx.interior = 0;
|
||||
}
|
||||
|
||||
return vtx;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,13 @@
|
||||
#define PS_ATST_NOTEQUAL 4
|
||||
#endif
|
||||
|
||||
#ifndef PS_AA1_NONE
|
||||
#define PS_AA1_NONE 0
|
||||
#define PS_AA1_LINE 1
|
||||
#define PS_AA1_TRIANGLE 2
|
||||
#define PS_AA1_TRIANGLE_SW_Z 3
|
||||
#endif
|
||||
|
||||
// TEX_COORD_DEBUG output the uv coordinate as color. It is useful
|
||||
// to detect bad sampling due to upscaling
|
||||
//#define TEX_COORD_DEBUG
|
||||
@@ -50,10 +57,11 @@
|
||||
#define NEEDS_RT_FOR_AFAIL (PS_AFAIL == PS_ZB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define NEEDS_DEPTH_FOR_AFAIL (PS_AFAIL == AFAIL_FB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define NEEDS_DEPTH_FOR_ZTST (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER)
|
||||
#define NEEDS_DEPTH_FOR_AA1 (PS_AA1 == PS_AA1_TRIANGLE_SW_Z)
|
||||
|
||||
#define NEEDS_RT (NEEDS_RT_EARLY || NEEDS_RT_FOR_AFAIL || (!PS_PRIMID_INIT && (PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW)) || PS_COLOR_FEEDBACK)
|
||||
#define NEEDS_TEX (PS_TFX != 4)
|
||||
#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST)
|
||||
#define SW_DEPTH (NEEDS_DEPTH_FOR_AFAIL || NEEDS_DEPTH_FOR_ZTST || NEEDS_DEPTH_FOR_AA1)
|
||||
#define ZWRITE (SW_DEPTH || PS_ZCLAMP || PS_ZFLOOR)
|
||||
|
||||
layout(std140, binding = 0) uniform cb21
|
||||
@@ -97,6 +105,9 @@ in SHADER
|
||||
#else
|
||||
flat vec4 c;
|
||||
#endif
|
||||
|
||||
float inv_cov; // We use the inverse to make it simpler to interpolate.
|
||||
flat uint interior; // 1 for triangle interior; 0 for edge;
|
||||
} PSin;
|
||||
|
||||
#define TARGET_0_QUALIFIER out
|
||||
@@ -182,6 +193,154 @@ vec4 sample_from_depth()
|
||||
|
||||
#if NEEDS_TEX
|
||||
|
||||
#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1)
|
||||
float manual_lod(float uv_w)
|
||||
{
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(uv_w)) * L;
|
||||
// FIXME max useful ?
|
||||
//return max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
return min(gs_lod, max_lod) - bias;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// 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_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
|
||||
|
||||
if (!(d_zero || d_par || d_per || d_inf_nan))
|
||||
{
|
||||
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);
|
||||
|
||||
vec2 new_dX = vec2(
|
||||
sqrt(F * (t + p) / (t * (q + t))),
|
||||
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
|
||||
);
|
||||
|
||||
vec2 new_dY = vec2(
|
||||
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
|
||||
sqrt(F * (t + p) / (t * (q - t)))
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
float aniso_ratio;
|
||||
float length_lod;
|
||||
vec2 aniso_line;
|
||||
if (length_major <= 1.0f)
|
||||
{
|
||||
// A zero length_major would result in NaN Lod and break sampling.
|
||||
// A small length_major would result in aniso_ratio getting clamped to 1.
|
||||
// Perform isotropic filtering instead.
|
||||
aniso_ratio = 1.0f;
|
||||
length_lod = length_major;
|
||||
aniso_line = vec2(0, 0);
|
||||
}
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#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
|
||||
#endif
|
||||
|
||||
vec4 colour;
|
||||
if (aniso_ratio == 1.0f)
|
||||
colour = textureLod(TextureSampler, uv, lod);
|
||||
else
|
||||
{
|
||||
vec4 num = vec4(0, 0, 0, 0);
|
||||
for (int i = 0; i < aniso_ratio; i++)
|
||||
{
|
||||
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
|
||||
vec2 uv_sample = uv + d;
|
||||
vec4 sample_colour = textureLod(TextureSampler, uv_sample, lod);
|
||||
num += sample_colour;
|
||||
}
|
||||
|
||||
colour = num / aniso_ratio;
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 sample_c(vec2 uv)
|
||||
{
|
||||
#if PS_TEX_IS_FB == 1
|
||||
@@ -205,21 +364,12 @@ vec4 sample_c(vec2 uv)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PS_AUTOMATIC_LOD == 1
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
return sample_c_af(uv, PSin.t_float.w);
|
||||
#elif PS_AUTOMATIC_LOD == 1
|
||||
return texture(TextureSampler, uv);
|
||||
#elif PS_MANUAL_LOD == 1
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(PSin.t_float.w)) * L;
|
||||
// FIXME max useful ?
|
||||
//float lod = max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
float lod = min(gs_lod, max_lod) - bias;
|
||||
|
||||
return textureLod(TextureSampler, uv, lod);
|
||||
return textureLod(TextureSampler, uv, manual_lod(PSin.t_float.w));
|
||||
#else
|
||||
return textureLod(TextureSampler, uv, 0.0f); // No lod
|
||||
#endif
|
||||
@@ -1099,7 +1249,15 @@ void ps_main()
|
||||
|
||||
vec4 C = ps_color();
|
||||
|
||||
#if PS_FIXED_ONE_A
|
||||
#if PS_AA1
|
||||
float cov = clamp(1.0f - abs(PSin.inv_cov), 0.0f, 1.0f);
|
||||
#if PS_ABE
|
||||
if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128.
|
||||
C.a = 128.0f * cov;
|
||||
#else
|
||||
C.a = 128.0f * cov;
|
||||
#endif
|
||||
#elif PS_FIXED_ONE_A
|
||||
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
||||
C.a = 128.0f;
|
||||
#endif
|
||||
@@ -1172,10 +1330,7 @@ void ps_main()
|
||||
uvec2 denorm_TA = uvec2(vec2(TA.xy) * 255.0f + 0.5f);
|
||||
|
||||
C.rb = vec2(float((denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7u) << 5)));
|
||||
if (bool(denorm_c.a & 0x80u))
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80u)));
|
||||
else
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
|
||||
C.ga = vec2(float((denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80u)));
|
||||
#elif PS_SHUFFLE_ACROSS
|
||||
#if(PS_PROCESS_BA == SHUFFLE_READWRITE && PS_PROCESS_RG == SHUFFLE_READWRITE)
|
||||
C.br = C.rb;
|
||||
@@ -1224,7 +1379,7 @@ void ps_main()
|
||||
#elif (PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
if (!atst_pass)
|
||||
{
|
||||
C.a = sample_from_rt().a;
|
||||
C.a = sample_from_rt().a;
|
||||
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
|
||||
input_z = sample_from_depth().r;
|
||||
#endif
|
||||
@@ -1244,6 +1399,11 @@ void ps_main()
|
||||
input_z = min(input_z, MaxDepthPS);
|
||||
#endif
|
||||
|
||||
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
|
||||
if (!bool(PSin.interior))
|
||||
input_z = sample_from_depth().r; // No depth update for triangle edges.
|
||||
#endif
|
||||
|
||||
#if ZWRITE
|
||||
#if SW_DEPTH && PS_NO_COLOR1 && (DEPTH_FEEDBACK_SUPPORT == 2)
|
||||
// Depth as color write. For depth as color feedback we write to both
|
||||
|
||||
@@ -18,6 +18,15 @@ layout(std140, binding = 1) uniform cb20
|
||||
|
||||
#ifdef VERTEX_SHADER
|
||||
|
||||
#ifndef VS_EXPAND_NONE
|
||||
#define VS_EXPAND_NONE 0
|
||||
#define VS_EXPAND_POINT 1
|
||||
#define VS_EXPAND_LINE 2
|
||||
#define VS_EXPAND_SPRITE 3
|
||||
#define VS_EXPAND_LINE_AA1 4
|
||||
#define VS_EXPAND_TRIANGLE_AA1 5
|
||||
#endif
|
||||
|
||||
out SHADER
|
||||
{
|
||||
vec4 t_float;
|
||||
@@ -27,11 +36,13 @@ out SHADER
|
||||
#else
|
||||
flat vec4 c;
|
||||
#endif
|
||||
float inv_cov; // We use the inverse to make it simpler to interpolate.
|
||||
flat uint interior; // 1 for triangle interior; 0 for edge.
|
||||
} VSout;
|
||||
|
||||
const float exp_min32 = exp2(-32.0f);
|
||||
|
||||
#if VS_EXPAND == 0
|
||||
#if VS_EXPAND == VS_EXPAND_NONE
|
||||
|
||||
layout(location = 0) in vec2 i_st;
|
||||
layout(location = 2) in vec4 i_c;
|
||||
@@ -98,10 +109,23 @@ struct RawVertex
|
||||
uint FOG;
|
||||
};
|
||||
|
||||
layout(std140, binding = 4) uniform cb22
|
||||
{
|
||||
uint BaseVertex;
|
||||
uint BaseIndex;
|
||||
uint pad_cb22_0;
|
||||
uint pad_cb22_1;
|
||||
};
|
||||
|
||||
layout(std140, binding = 2) readonly buffer VertexBuffer {
|
||||
RawVertex vertex_buffer[];
|
||||
};
|
||||
|
||||
// Warning: use std430 instead of std140 so that the ints are tightly packed.
|
||||
layout(std430, binding = 3) readonly buffer IndexBuffer {
|
||||
uint index_buffer[];
|
||||
};
|
||||
|
||||
struct ProcessedVertex
|
||||
{
|
||||
vec4 p;
|
||||
@@ -110,9 +134,17 @@ struct ProcessedVertex
|
||||
vec4 c;
|
||||
};
|
||||
|
||||
uint load_index(uint _i)
|
||||
{
|
||||
uint i = _i + BaseIndex;
|
||||
// i is even => load lower 16 bits; i odd => load upper 16 bits.
|
||||
uint shift = (i & 1u) << 4u;
|
||||
return (index_buffer[i >> 1u] >> shift) & 0xFFFFu;
|
||||
}
|
||||
|
||||
ProcessedVertex load_vertex(uint index)
|
||||
{
|
||||
RawVertex rvtx = vertex_buffer[index];
|
||||
RawVertex rvtx = vertex_buffer[BaseVertex + index];
|
||||
|
||||
vec2 i_st = rvtx.ST;
|
||||
vec4 i_c = vec4(uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
|
||||
@@ -156,14 +188,14 @@ void main()
|
||||
|
||||
uint vid = uint(gl_VertexID);
|
||||
|
||||
#if VS_EXPAND == 1 // Point
|
||||
#if VS_EXPAND == VS_EXPAND_POINT
|
||||
|
||||
vtx = load_vertex(vid >> 2);
|
||||
|
||||
vtx.p.x += ((vid & 1u) != 0u) ? PointSize.x : 0.0f;
|
||||
vtx.p.y += ((vid & 2u) != 0u) ? PointSize.y : 0.0f;
|
||||
|
||||
#elif VS_EXPAND == 2 // Line
|
||||
#elif (VS_EXPAND == VS_EXPAND_LINE) || (VS_EXPAND == VS_EXPAND_LINE_AA1)
|
||||
|
||||
uint vid_base = vid >> 2;
|
||||
bool is_bottom = (vid & 2u) != 0u;
|
||||
@@ -172,18 +204,29 @@ void main()
|
||||
vtx = load_vertex(vid_base);
|
||||
ProcessedVertex other = load_vertex(vid_other);
|
||||
|
||||
vec2 line_vector = normalize(vtx.p.xy - other.p.xy);
|
||||
vec2 line_normal = vec2(line_vector.y, -line_vector.x);
|
||||
vec2 line_width = (line_normal * PointSize) / 2;
|
||||
// line_normal is inverted for bottom point
|
||||
vec2 offset = ((uint(is_bottom) ^ uint(is_right)) != 0u) ? line_width : -line_width;
|
||||
// Use bottom minus top for delta regardless of which vertex we are expanding.
|
||||
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
|
||||
vec2 line_vector = normalize(line_delta);
|
||||
#if VS_EXPAND == VS_EXPAND_LINE
|
||||
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
|
||||
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
// Expand in y direction for shallow lines and x direction for steep lines.
|
||||
line_delta /= VertexScale;
|
||||
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
|
||||
#endif
|
||||
vec2 line_width = (line_expand * PointSize) / 2;
|
||||
vec2 offset = is_right ? line_width : -line_width;
|
||||
vtx.p.xy += offset;
|
||||
|
||||
#if VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
VSout.inv_cov = is_right ? 1.0f : -1.0f;
|
||||
#endif
|
||||
|
||||
// Lines will be run as (0 1 2) (1 2 3)
|
||||
// This means that both triangles will have a point based off the top line point as their first point
|
||||
// So we don't have to do anything for !IIP
|
||||
|
||||
#elif VS_EXPAND == 3 // Sprite
|
||||
#elif VS_EXPAND == VS_EXPAND_SPRITE
|
||||
|
||||
// Sprite points are always in pairs
|
||||
uint vid_base = vid >> 1;
|
||||
@@ -204,6 +247,69 @@ void main()
|
||||
vtx.t_float.y = is_bottom ? lt.t_float.y : vtx.t_float.y;
|
||||
vtx.t_int.yw = is_bottom ? lt.t_int.yw : vtx.t_int.yw;
|
||||
|
||||
#elif VS_EXPAND == VS_EXPAND_TRIANGLE_AA1
|
||||
|
||||
// Triangles with AA1 are expanded as follows:
|
||||
// - Vertices 0-2: Interior of triangle (1 triangle).
|
||||
// - Vertices 3-8: First edge expanded (2 triangles).
|
||||
// - Vertices 9-14: Second edge expanded (2 triangles).
|
||||
// - Vertices 15-20: Third edge expanded (2 triangles).
|
||||
|
||||
uint prim_id = vid / 21;
|
||||
uint prim_offset = vid - 21 * prim_id; // range: 0-20
|
||||
bool interior = prim_offset < 3;
|
||||
|
||||
if (interior)
|
||||
{
|
||||
vtx = load_vertex(load_index(3 * prim_id + prim_offset));
|
||||
VSout.inv_cov = 0.0f; // Full coverage
|
||||
VSout.interior = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
|
||||
uint prim_offset_edges = prim_offset - 3; // range: 0-17
|
||||
uint i0 = prim_offset_edges / 6;
|
||||
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
||||
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
||||
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
|
||||
|
||||
// Note: order of top/bottom, inside/outside order is arbitrary,
|
||||
// as long as it assembles into two triangles forming a quad.
|
||||
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
|
||||
bool is_outside = (edge_offset & 1) != 0;
|
||||
|
||||
vtx = load_vertex(load_index(3 * prim_id + i0));
|
||||
ProcessedVertex other = load_vertex(load_index(3 * prim_id + i1));
|
||||
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + i2));
|
||||
|
||||
// Similar expansion to line AA1 except instead of expanding on both sides of
|
||||
// the line we expand on on the side towards the outside of the triangle.
|
||||
vec2 line_delta = vtx.p.xy - other.p.xy;
|
||||
vec2 line_normal = normalize(vec2(line_delta.y, -line_delta.x));
|
||||
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
|
||||
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
|
||||
{
|
||||
// Expand direction point towards the interior so flip it.
|
||||
line_expand = -line_expand;
|
||||
}
|
||||
vec2 line_width = (line_expand * PointSize) / 2;
|
||||
|
||||
if (is_bottom)
|
||||
vtx = other;
|
||||
if (is_outside)
|
||||
{
|
||||
vtx.p.xy += line_width;
|
||||
VSout.inv_cov = 1.0f; // No coverage
|
||||
}
|
||||
else
|
||||
{
|
||||
VSout.inv_cov = 0.0f; // Full coverage
|
||||
}
|
||||
|
||||
VSout.interior = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
gl_Position = vtx.p;
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
|
||||
#if defined(VERTEX_SHADER)
|
||||
|
||||
#ifndef VS_EXPAND_NONE
|
||||
#define VS_EXPAND_NONE 0
|
||||
#define VS_EXPAND_POINT 1
|
||||
#define VS_EXPAND_LINE 2
|
||||
#define VS_EXPAND_SPRITE 3
|
||||
#define VS_EXPAND_LINE_AA1 4
|
||||
#define VS_EXPAND_TRIANGLE_AA1 5
|
||||
#endif
|
||||
|
||||
layout(std140, set = 0, binding = 0) uniform cb0
|
||||
{
|
||||
vec2 VertexScale;
|
||||
@@ -28,9 +37,12 @@ layout(location = 0) out VSOutput
|
||||
#else
|
||||
flat vec4 c;
|
||||
#endif
|
||||
|
||||
float inv_cov; // We use the inverse to make it simpler to interpolate.
|
||||
flat uint interior; // 1 for triangle interior; 0 for edge;
|
||||
} vsOut;
|
||||
|
||||
#if VS_EXPAND == 0
|
||||
#if VS_EXPAND == VS_EXPAND_NONE
|
||||
|
||||
layout(location = 0) in vec2 a_st;
|
||||
layout(location = 1) in uvec4 a_c;
|
||||
@@ -99,10 +111,23 @@ struct RawVertex
|
||||
uint FOG;
|
||||
};
|
||||
|
||||
layout(push_constant) uniform cb2
|
||||
{
|
||||
uint BaseVertex;
|
||||
uint BaseIndex;
|
||||
uint pad_cb2_0;
|
||||
uint pad_cb2_1;
|
||||
};
|
||||
|
||||
layout(std140, set = 0, binding = 2) readonly buffer VertexBuffer {
|
||||
RawVertex vertex_buffer[];
|
||||
};
|
||||
|
||||
// Warning: use std430 instead of std140 so that the ints are tightly packed.
|
||||
layout(std430, set = 0, binding = 3) readonly buffer IndexBuffer {
|
||||
uint index_buffer[];
|
||||
};
|
||||
|
||||
struct ProcessedVertex
|
||||
{
|
||||
vec4 p;
|
||||
@@ -111,9 +136,17 @@ struct ProcessedVertex
|
||||
vec4 c;
|
||||
};
|
||||
|
||||
uint load_index(uint _i)
|
||||
{
|
||||
uint i = _i + BaseIndex;
|
||||
// i is even => load lower 16 bits; i odd => load upper 16 bits.
|
||||
uint shift = (i & 1u) << 4u;
|
||||
return (index_buffer[i >> 1u] >> shift) & 0xFFFFu;
|
||||
}
|
||||
|
||||
ProcessedVertex load_vertex(uint index)
|
||||
{
|
||||
RawVertex rvtx = vertex_buffer[index];
|
||||
RawVertex rvtx = vertex_buffer[BaseVertex + index];
|
||||
|
||||
vec2 a_st = rvtx.ST;
|
||||
uvec4 a_c = uvec4(bitfieldExtract(rvtx.RGBA, 0, 8), bitfieldExtract(rvtx.RGBA, 8, 8),
|
||||
@@ -161,14 +194,14 @@ void main()
|
||||
ProcessedVertex vtx;
|
||||
uint vid = uint(gl_VertexIndex);
|
||||
|
||||
#if VS_EXPAND == 1 // Point
|
||||
#if VS_EXPAND == VS_EXPAND_POINT
|
||||
|
||||
vtx = load_vertex(vid >> 2);
|
||||
|
||||
vtx.p.x += ((vid & 1u) != 0u) ? PointSize.x : 0.0f;
|
||||
vtx.p.y += ((vid & 2u) != 0u) ? PointSize.y : 0.0f;
|
||||
|
||||
#elif VS_EXPAND == 2 // Line
|
||||
#elif (VS_EXPAND == VS_EXPAND_LINE) || (VS_EXPAND == VS_EXPAND_LINE_AA1)
|
||||
|
||||
uint vid_base = vid >> 2;
|
||||
|
||||
@@ -179,18 +212,29 @@ void main()
|
||||
vtx = load_vertex(vid_base);
|
||||
ProcessedVertex other = load_vertex(vid_other);
|
||||
|
||||
vec2 line_vector = normalize(vtx.p.xy - other.p.xy);
|
||||
vec2 line_normal = vec2(line_vector.y, -line_vector.x);
|
||||
vec2 line_width = (line_normal * PointSize) / 2;
|
||||
// line_normal is inverted for bottom point
|
||||
vec2 offset = ((uint(is_bottom) ^ uint(is_right)) != 0u) ? line_width : -line_width;
|
||||
// Use bottom minus top for delta regardless of which vertex we are expanding.
|
||||
vec2 line_delta = is_bottom ? (vtx.p.xy - other.p.xy) : (other.p.xy - vtx.p.xy);
|
||||
vec2 line_vector = normalize(line_delta);
|
||||
#if VS_EXPAND == VS_EXPAND_LINE
|
||||
vec2 line_expand = vec2(line_vector.y, -line_vector.x);
|
||||
#elif VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
// Expand in y direction for shallow lines and x direction for steep lines.
|
||||
line_delta /= VertexScale;
|
||||
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
|
||||
#endif
|
||||
vec2 line_width = (line_expand * PointSize) / 2;
|
||||
vec2 offset = is_right ? line_width : -line_width;
|
||||
vtx.p.xy += offset;
|
||||
|
||||
#if VS_EXPAND == VS_EXPAND_LINE_AA1
|
||||
vsOut.inv_cov = is_right ? 1.0f : -1.0f;
|
||||
#endif
|
||||
|
||||
// Lines will be run as (0 1 2) (1 2 3)
|
||||
// This means that both triangles will have a point based off the top line point as their first point
|
||||
// So we don't have to do anything for !IIP
|
||||
|
||||
#elif VS_EXPAND == 3 // Sprite
|
||||
#elif VS_EXPAND == VS_EXPAND_SPRITE
|
||||
|
||||
// Sprite points are always in pairs
|
||||
uint vid_base = vid >> 1;
|
||||
@@ -211,6 +255,69 @@ void main()
|
||||
vtx.t.y = is_bottom ? lt.t.y : vtx.t.y;
|
||||
vtx.ti.yw = is_bottom ? lt.ti.yw : vtx.ti.yw;
|
||||
|
||||
#elif VS_EXPAND == VS_EXPAND_TRIANGLE_AA1
|
||||
|
||||
// Triangles with AA1 are expanded as follows:
|
||||
// - Vertices 0-2: Interior of triangle (1 triangle).
|
||||
// - Vertices 3-8: First edge expanded (2 triangles).
|
||||
// - Vertices 9-14: Second edge expanded (2 triangles).
|
||||
// - Vertices 15-20: Third edge expanded (2 triangles).
|
||||
|
||||
uint prim_id = vid / 21;
|
||||
uint prim_offset = vid - 21 * prim_id; // range: 0-20
|
||||
bool interior = prim_offset < 3;
|
||||
|
||||
if (interior)
|
||||
{
|
||||
vtx = load_vertex(load_index(3 * prim_id + prim_offset));
|
||||
vsOut.inv_cov = 0.0f; // Full coverage
|
||||
vsOut.interior = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
|
||||
uint prim_offset_edges = prim_offset - 3; // range: 0-17
|
||||
uint i0 = prim_offset_edges / 6;
|
||||
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
||||
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
||||
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
|
||||
|
||||
// Note: order of top/bottom, inside/outside order is arbitrary,
|
||||
// as long as it assembles into two triangles forming a quad.
|
||||
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
|
||||
bool is_outside = (edge_offset & 1) != 0;
|
||||
|
||||
vtx = load_vertex(load_index(3 * prim_id + i0));
|
||||
ProcessedVertex other = load_vertex(load_index(3 * prim_id + i1));
|
||||
ProcessedVertex opposite = load_vertex(load_index(3 * prim_id + i2));
|
||||
|
||||
// Similar expansion to line AA1 except instead of expanding on both sides of
|
||||
// the line we expand on on the side towards the outside of the triangle.
|
||||
vec2 line_delta = vtx.p.xy - other.p.xy;
|
||||
vec2 line_normal = normalize(vec2(line_delta.y, -line_delta.x));
|
||||
vec2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? vec2(0.0f, 2.0f) : vec2(2.0f, 0.0f);
|
||||
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - vtx.p.xy, line_normal) >= 0.0f))
|
||||
{
|
||||
// Expand direction point towards the interior so flip it.
|
||||
line_expand = -line_expand;
|
||||
}
|
||||
vec2 line_width = (line_expand * PointSize) / 2;
|
||||
|
||||
if (is_bottom)
|
||||
vtx = other;
|
||||
if (is_outside)
|
||||
{
|
||||
vtx.p.xy += line_width;
|
||||
vsOut.inv_cov = 1.0f; // No coverage
|
||||
}
|
||||
else
|
||||
{
|
||||
vsOut.inv_cov = 0.0f; // Full coverage
|
||||
}
|
||||
|
||||
vsOut.interior = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
gl_Position = vtx.p;
|
||||
@@ -267,6 +374,13 @@ void main()
|
||||
#define PS_ATST_NOTEQUAL 4
|
||||
#endif
|
||||
|
||||
#ifndef PS_AA1_NONE
|
||||
#define PS_AA1_NONE 0
|
||||
#define PS_AA1_LINE 1
|
||||
#define PS_AA1_TRIANGLE 2
|
||||
#define PS_AA1_TRIANGLE_SW_Z 3
|
||||
#endif
|
||||
|
||||
#ifndef PS_FST
|
||||
#define PS_FST 0
|
||||
#define PS_WMS 0
|
||||
@@ -327,9 +441,10 @@ void main()
|
||||
#define AFAIL_NEEDS_RT (PS_AFAIL == AFAIL_ZB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define AFAIL_NEEDS_DEPTH (PS_AFAIL == AFAIL_FB_ONLY || PS_AFAIL == AFAIL_RGB_ONLY_SW_Z)
|
||||
#define ZTST_NEEDS_DEPTH (PS_ZTST == ZTST_GEQUAL || PS_ZTST == ZTST_GREATER)
|
||||
#define AA1_NEEDS_DEPTH (PS_AA1 == PS_AA1_TRIANGLE_SW_Z)
|
||||
|
||||
#define PS_FEEDBACK_LOOP_IS_NEEDED_RT (PS_TEX_IS_FB == 1 || AFAIL_NEEDS_RT || PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW || (PS_DATE >= 5) || AFAIL_NEEDS_RT)
|
||||
#define PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH (AFAIL_NEEDS_DEPTH || ZTST_NEEDS_DEPTH)
|
||||
#define PS_FEEDBACK_LOOP_IS_NEEDED_RT (PS_TEX_IS_FB == 1 || AFAIL_NEEDS_RT || PS_FBMASK || SW_BLEND_NEEDS_RT || SW_AD_TO_HW || (PS_DATE >= 5))
|
||||
#define PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH (AFAIL_NEEDS_DEPTH || ZTST_NEEDS_DEPTH || AA1_NEEDS_DEPTH)
|
||||
#define ZWRITE (PS_ZCLAMP || PS_ZFLOOR || PS_FEEDBACK_LOOP_IS_NEEDED_DEPTH)
|
||||
|
||||
#define NEEDS_TEX (PS_TFX != 4)
|
||||
@@ -365,6 +480,8 @@ layout(location = 0) in VSOutput
|
||||
#else
|
||||
flat vec4 c;
|
||||
#endif
|
||||
float inv_cov; // We use the inverse to make it simpler to interpolate.
|
||||
flat uint interior; // 1 for triangle interior; 0 for edge;
|
||||
} vsIn;
|
||||
|
||||
#if !PS_NO_COLOR && !PS_NO_COLOR1
|
||||
@@ -416,6 +533,154 @@ layout(depth_less) out float gl_FragDepth;
|
||||
|
||||
#if NEEDS_TEX
|
||||
|
||||
#if (PS_AUTOMATIC_LOD != 1) && (PS_MANUAL_LOD == 1)
|
||||
float manual_lod(float uv_w)
|
||||
{
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(uv_w)) * L;
|
||||
// FIXME max useful ?
|
||||
//return max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
return min(gs_lod, max_lod) - bias;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// 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_inf_nan = any(isinf(dX)) || any(isinf(dY)) || any(isnan(dX)) || any(isnan(dY));
|
||||
|
||||
if (!(d_zero || d_par || d_per || d_inf_nan))
|
||||
{
|
||||
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);
|
||||
|
||||
vec2 new_dX = vec2(
|
||||
sqrt(F * (t + p) / (t * (q + t))),
|
||||
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
|
||||
);
|
||||
|
||||
vec2 new_dY = vec2(
|
||||
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
|
||||
sqrt(F * (t + p) / (t * (q - t)))
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
float aniso_ratio;
|
||||
float length_lod;
|
||||
vec2 aniso_line;
|
||||
if (length_major <= 1.0f)
|
||||
{
|
||||
// A zero length_major would result in NaN Lod and break sampling.
|
||||
// A small length_major would result in aniso_ratio getting clamped to 1.
|
||||
// Perform isotropic filtering instead.
|
||||
aniso_ratio = 1.0f;
|
||||
length_lod = length_major;
|
||||
aniso_line = vec2(0, 0);
|
||||
}
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
#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
|
||||
#endif
|
||||
|
||||
vec4 colour;
|
||||
if (aniso_ratio == 1.0f)
|
||||
colour = textureLod(Texture, uv, lod);
|
||||
else
|
||||
{
|
||||
vec4 num = vec4(0, 0, 0, 0);
|
||||
for (int i = 0; i < aniso_ratio; i++)
|
||||
{
|
||||
vec2 d = -aniso_line + (0.5f + i) * (2.0f * aniso_line) / aniso_ratio;
|
||||
vec2 uv_sample = uv + d;
|
||||
vec4 sample_colour = textureLod(Texture, uv_sample, lod);
|
||||
num += sample_colour;
|
||||
}
|
||||
|
||||
colour = num / aniso_ratio;
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 sample_c(vec2 uv)
|
||||
{
|
||||
#if PS_TEX_IS_FB
|
||||
@@ -439,21 +704,12 @@ vec4 sample_c(vec2 uv)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if PS_AUTOMATIC_LOD == 1
|
||||
#if PS_ANISOTROPIC_FILTERING > 1
|
||||
return sample_c_af(uv, vsIn.t.w);
|
||||
#elif PS_AUTOMATIC_LOD == 1
|
||||
return texture(Texture, uv);
|
||||
#elif PS_MANUAL_LOD == 1
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = LODParams.x;
|
||||
float L = LODParams.y;
|
||||
float bias = LODParams.z;
|
||||
float max_lod = LODParams.w;
|
||||
|
||||
float gs_lod = K - log2(abs(vsIn.t.w)) * L;
|
||||
// FIXME max useful ?
|
||||
//float lod = max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
float lod = min(gs_lod, max_lod) - bias;
|
||||
|
||||
return textureLod(Texture, uv, lod);
|
||||
return textureLod(Texture, uv, manual_lod(vsIn.t.w));
|
||||
#else
|
||||
return textureLod(Texture, uv, 0); // No lod
|
||||
#endif
|
||||
@@ -1346,7 +1602,15 @@ void main()
|
||||
|
||||
vec4 C = ps_color();
|
||||
|
||||
#if PS_FIXED_ONE_A
|
||||
#if PS_AA1
|
||||
float cov = clamp(1.0f - abs(vsIn.inv_cov), 0.0f, 1.0f);
|
||||
#if PS_ABE
|
||||
if (floor(C.a) == 128.0f) // The coverage is only used if the fragment alpha is 128.
|
||||
C.a = 128.0f * cov;
|
||||
#else
|
||||
C.a = 128.0f * cov;
|
||||
#endif
|
||||
#elif PS_FIXED_ONE_A
|
||||
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
||||
C.a = 128.0f;
|
||||
#endif
|
||||
@@ -1472,7 +1736,7 @@ void main()
|
||||
#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;
|
||||
#if PS_AFAIL == AFAIL_RGB_ONLY_SW_Z
|
||||
input_z = sample_from_depth().r;
|
||||
#endif
|
||||
@@ -1483,7 +1747,12 @@ void main()
|
||||
#if PS_ZCLAMP
|
||||
input_z = min(input_z, MaxDepthPS);
|
||||
#endif
|
||||
|
||||
|
||||
#if PS_AA1 == PS_AA1_TRIANGLE_SW_Z
|
||||
if (!bool(vsIn.interior))
|
||||
input_z = sample_from_depth().r; // No depth update for triangle edges.
|
||||
#endif
|
||||
|
||||
#if ZWRITE
|
||||
gl_FragDepth = input_z;
|
||||
#endif
|
||||
|
||||
@@ -247,6 +247,9 @@ if (MSVC)
|
||||
else()
|
||||
set(DEFAULT_WARNINGS -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wno-missing-field-initializers)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
list(APPEND DEFAULT_WARNINGS -Wno-attributes)
|
||||
endif()
|
||||
|
||||
if (USE_PGO_GENERATE OR USE_PGO_OPTIMIZE)
|
||||
add_compile_options("-fprofile-dir=${CMAKE_SOURCE_DIR}/profile")
|
||||
|
||||
@@ -226,11 +226,6 @@ namespace PageFaultHandler
|
||||
|
||||
#ifdef ARCH_ARM64
|
||||
|
||||
void HostSys::FlushInstructionCache(void* address, u32 size)
|
||||
{
|
||||
__builtin___clear_cache(reinterpret_cast<char*>(address), reinterpret_cast<char*>(address) + size);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
|
||||
{
|
||||
u32 bits;
|
||||
|
||||
@@ -799,6 +799,12 @@ bool GSRunner::ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& pa
|
||||
s_perf_enable = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("-debugdevice"))
|
||||
{
|
||||
Console.WriteLn("Enable debug device");
|
||||
s_settings_interface.SetBoolValue("EmuCore/GS", "UseDebugDevice", true);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("--"))
|
||||
{
|
||||
no_more_args = true;
|
||||
|
||||
@@ -450,7 +450,7 @@ void DebuggerWindow::onStepInto()
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
|
||||
repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DebuggerWindow::onStepOver()
|
||||
@@ -500,7 +500,7 @@ void DebuggerWindow::onStepOver()
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DebuggerWindow::onStepOut()
|
||||
@@ -525,8 +525,7 @@ void DebuggerWindow::onStepOut()
|
||||
cpu->getPC(),
|
||||
cpu->getRegister(0, 31),
|
||||
cpu->getRegister(0, 29),
|
||||
thread->EntryPoint(),
|
||||
thread->StackTop());
|
||||
thread->EntryPoint());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -541,7 +540,7 @@ void DebuggerWindow::onStepOut()
|
||||
cpu->resumeCpu();
|
||||
});
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DebuggerWindow::changeEvent(QEvent* event)
|
||||
|
||||
@@ -86,7 +86,7 @@ bool DisassemblyView::fromJson(const JsonValueWrapper& json)
|
||||
if (show_instruction_bytes != json.value().MemberEnd() && show_instruction_bytes->value.IsBool())
|
||||
m_showInstructionBytes = show_instruction_bytes->value.GetBool();
|
||||
|
||||
repaint();
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -222,7 +222,7 @@ void DisassemblyView::contextRunToCursor()
|
||||
void DisassemblyView::contextJumpToCursor()
|
||||
{
|
||||
cpu().setPc(m_selectedAddressStart);
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DisassemblyView::contextToggleBreakpoint()
|
||||
@@ -378,7 +378,7 @@ void DisassemblyView::contextRestoreFunction()
|
||||
void DisassemblyView::contextShowInstructionBytes()
|
||||
{
|
||||
m_showInstructionBytes = !m_showInstructionBytes;
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
QString DisassemblyView::GetLineDisasm(u32 address)
|
||||
@@ -610,7 +610,7 @@ void DisassemblyView::mousePressEvent(QMouseEvent* event)
|
||||
m_selectedAddressEnd = selectedAddress;
|
||||
}
|
||||
}
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,7 +637,7 @@ void DisassemblyView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
m_visibleStart -= 4;
|
||||
}
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
@@ -709,7 +709,7 @@ void DisassemblyView::keyPressEvent(QKeyEvent* event)
|
||||
break;
|
||||
}
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void DisassemblyView::openContextMenu(QPoint pos)
|
||||
@@ -1014,9 +1014,9 @@ void DisassemblyView::gotoAddress(u32 address, bool should_set_focus)
|
||||
m_selectedAddressStart = destAddress;
|
||||
m_selectedAddressEnd = destAddress;
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
if (should_set_focus)
|
||||
this->setFocus();
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void DisassemblyView::toggleBreakpoint(u32 address)
|
||||
@@ -1034,7 +1034,7 @@ void DisassemblyView::toggleBreakpoint(u32 address)
|
||||
QtHost::RunOnUIThread([view, cpu]() {
|
||||
BreakpointModel::getInstance(*cpu)->refreshData();
|
||||
if (view)
|
||||
view->repaint();
|
||||
view->update();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ MemorySearchView::MemorySearchView(const DebuggerViewParameters& parameters)
|
||||
: DebuggerView(parameters, MONOSPACE_FONT)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
this->repaint();
|
||||
|
||||
m_ui.listSearchResults->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui.btnSearch, &QPushButton::clicked, this, &MemorySearchView::onSearchButtonClicked);
|
||||
|
||||
@@ -741,7 +741,7 @@ bool MemoryView::fromJson(const JsonValueWrapper& json)
|
||||
if (little_endian != json.value().MemberEnd() && little_endian->value.IsBool())
|
||||
m_table.SetLittleEndian(little_endian->value.GetBool());
|
||||
|
||||
repaint();
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -764,7 +764,7 @@ void MemoryView::mousePressEvent(QMouseEvent* event)
|
||||
return;
|
||||
|
||||
m_table.SelectAt(event->pos());
|
||||
repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryView::openContextMenu(QPoint pos)
|
||||
@@ -859,7 +859,7 @@ void MemoryView::openContextMenu(QPoint pos)
|
||||
|
||||
menu->popup(this->mapToGlobal(pos));
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -934,7 +934,7 @@ void MemoryView::wheelEvent(QWheelEvent* event)
|
||||
{
|
||||
m_table.UpdateStartAddress(m_table.startAddress - 0x10);
|
||||
}
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryView::keyPressEvent(QKeyEvent* event)
|
||||
@@ -954,7 +954,7 @@ void MemoryView::keyPressEvent(QKeyEvent* event)
|
||||
break;
|
||||
}
|
||||
}
|
||||
this->repaint();
|
||||
update();
|
||||
DebuggerView::broadcastEvent(DebuggerEvents::VMUpdate());
|
||||
}
|
||||
|
||||
@@ -962,8 +962,8 @@ void MemoryView::gotoAddress(u32 address)
|
||||
{
|
||||
m_table.UpdateStartAddress(address & ~0xF);
|
||||
m_table.selectedAddress = address;
|
||||
this->repaint();
|
||||
this->setFocus();
|
||||
update();
|
||||
setFocus();
|
||||
}
|
||||
|
||||
#include "moc_MemoryView.cpp"
|
||||
|
||||
@@ -36,7 +36,7 @@ RegisterView::RegisterView(const DebuggerViewParameters& parameters)
|
||||
ui.registerTabs->addTab(cpu().getRegisterCategoryName(i));
|
||||
}
|
||||
|
||||
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { this->repaint(); });
|
||||
connect(ui.registerTabs, &QTabBar::currentChanged, [this]() { update(); });
|
||||
|
||||
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
||||
update();
|
||||
@@ -69,7 +69,7 @@ bool RegisterView::fromJson(const JsonValueWrapper& json)
|
||||
if (show_fpr_float != json.value().MemberEnd() && show_fpr_float->value.IsBool())
|
||||
m_showFPRFloat = show_fpr_float->value.GetBool();
|
||||
|
||||
repaint();
|
||||
update();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -209,7 +209,8 @@ void RegisterView::mousePressEvent(QMouseEvent* event)
|
||||
}
|
||||
}
|
||||
}
|
||||
this->repaint();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void RegisterView::wheelEvent(QWheelEvent* event)
|
||||
@@ -223,7 +224,7 @@ void RegisterView::wheelEvent(QWheelEvent* event)
|
||||
m_rowStart -= 1;
|
||||
}
|
||||
|
||||
this->repaint();
|
||||
update();
|
||||
}
|
||||
|
||||
void RegisterView::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
@@ -259,7 +260,7 @@ void RegisterView::customMenuRequested(QPoint pos)
|
||||
action->setChecked(m_showFPRFloat);
|
||||
connect(action, &QAction::triggered, this, [this]() {
|
||||
m_showFPRFloat = !m_showFPRFloat;
|
||||
repaint();
|
||||
update();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
@@ -272,7 +273,7 @@ void RegisterView::customMenuRequested(QPoint pos)
|
||||
action->setChecked(m_showVU0FFloat);
|
||||
connect(action, &QAction::triggered, this, [this]() {
|
||||
m_showVU0FFloat = !m_showVU0FFloat;
|
||||
repaint();
|
||||
update();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -111,8 +111,7 @@ void StackModel::refreshData()
|
||||
{
|
||||
if (thread->Status() == ThreadStatus::THS_RUN)
|
||||
{
|
||||
m_stackFrames = MipsStackWalk::Walk(&m_cpu, m_cpu.getPC(), m_cpu.getRegister(0, 31), m_cpu.getRegister(0, 29),
|
||||
thread->EntryPoint(), thread->StackTop());
|
||||
m_stackFrames = m_cpu.StackTrace(*thread);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +71,6 @@ private:
|
||||
//: Refers to a Thread Wait State in the Debugger.
|
||||
{WaitState::NONE, tr("NONE")},
|
||||
//: Refers to a Thread Wait State in the Debugger.
|
||||
{WaitState::WAKEUP_REQ, tr("WAKEUP REQUEST")},
|
||||
//: Refers to a Thread Wait State in the Debugger.
|
||||
{WaitState::SEMA, tr("SEMAPHORE")},
|
||||
//: Refers to a Thread Wait State in the Debugger.
|
||||
{WaitState::SLEEP, tr("SLEEP")},
|
||||
|
||||
@@ -294,11 +294,6 @@ void GameListWidget::initialize()
|
||||
if (Host::ContainsBaseSettingValue("GameListTableView", "HeaderState"))
|
||||
{
|
||||
loadTableHeaderState();
|
||||
// Enforce at least one column is visible immediately after loading.
|
||||
// This handles cases where a config (perhaps from an older version) has 0 columns and
|
||||
// no games are visible to be changed (such as per-game config) or played as you can't click on any.
|
||||
// Will automatically repair a broken header state from config (PCSX2.ini) file.
|
||||
ensureMinimumOneColumnVisible();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -811,6 +806,14 @@ void GameListWidget::loadTableHeaderState()
|
||||
|
||||
QSignalBlocker blocker(header);
|
||||
header->restoreState(QByteArray::fromBase64(QByteArray::fromStdString(state_setting)));
|
||||
|
||||
header->setSectionHidden(GameListModel::Column_Cover, true);
|
||||
|
||||
// Enforce at least one column is visible immediately after loading.
|
||||
// This handles cases where a config (perhaps from an older version) has 0 columns and
|
||||
// no games are visible to be changed (such as per-game config) or played as you can't click on any.
|
||||
// Will automatically repair a broken header state from config (PCSX2.ini) file.
|
||||
ensureMinimumOneColumnVisible();
|
||||
}
|
||||
|
||||
void GameListWidget::ensureMinimumOneColumnVisible()
|
||||
|
||||
+44
-4
@@ -873,6 +873,26 @@ void MainWindow::saveStateToConfig()
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (Host::ContainsBaseSettingValue("UI", "MainWindowMaximized"))
|
||||
{
|
||||
const bool maximized = Host::GetBaseBoolSettingValue("UI", "MainWindowMaximized");
|
||||
if (maximized != isMaximized())
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("UI", "MainWindowMaximized", isMaximized());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Host::ContainsBaseSettingValue("UI", "MainWindowFullscreen"))
|
||||
{
|
||||
const bool fullscreen = Host::GetBaseBoolSettingValue("UI", "MainWindowFullscreen");
|
||||
if (fullscreen != isFullScreen())
|
||||
{
|
||||
Host::SetBaseBoolSettingValue("UI", "MainWindowFullscreen", isFullScreen());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
Host::CommitBaseSettingChanges();
|
||||
}
|
||||
@@ -901,6 +921,24 @@ void MainWindow::restoreStateFromConfig()
|
||||
m_ui.actionViewStatusBar->setChecked(!m_ui.statusBar->isHidden());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (Host::ContainsBaseSettingValue("UI", "MainWindowMaximized"))
|
||||
{
|
||||
const bool maximized = Host::GetBaseBoolSettingValue("UI", "MainWindowMaximized");
|
||||
if (maximized)
|
||||
showMaximized();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (Host::ContainsBaseSettingValue("UI", "MainWindowFullscreen"))
|
||||
{
|
||||
const bool fullscreen = Host::GetBaseBoolSettingValue("UI", "MainWindowFullscreen");
|
||||
if (fullscreen)
|
||||
showFullScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::updateEmulationActions(bool starting, bool running, bool stopping)
|
||||
@@ -1114,7 +1152,7 @@ bool MainWindow::shouldHideMainWindow() const
|
||||
{
|
||||
// NOTE: We can't use isRenderingToMain() here, because this happens post-fullscreen-switch.
|
||||
return (Host::GetBoolSettingValue("UI", "HideMainWindowWhenRunning", false) && !g_emu_thread->shouldRenderToMain()) ||
|
||||
(g_emu_thread->shouldRenderToMain() && (isRenderingFullscreen() || m_is_temporarily_windowed)) ||
|
||||
(g_emu_thread->shouldRenderToMain() && (isRenderingFullscreen() || g_emu_thread->isExclusiveFullscreen() || m_is_temporarily_windowed)) ||
|
||||
Host::InNoGUIMode();
|
||||
}
|
||||
|
||||
@@ -1714,7 +1752,7 @@ void MainWindow::onStartFullscreenUITriggered()
|
||||
|
||||
void MainWindow::onFullscreenUIStateChange(bool running)
|
||||
{
|
||||
m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture Mode"));
|
||||
m_ui.actionStartFullscreenUI->setText(running ? tr("Stop Big Picture Mode") : tr("Start Big Picture &Mode"));
|
||||
m_ui.actionToolbarStartFullscreenUI->setText(running ? tr("Exit Big Picture", "In Toolbar") : tr("Big Picture", "In Toolbar"));
|
||||
}
|
||||
|
||||
@@ -2538,6 +2576,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
m_display_is_exclusive_fullscreen = g_emu_thread->isExclusiveFullscreen();
|
||||
g_emu_thread->connectDisplaySignals(m_display_surface);
|
||||
|
||||
updateWindowTitle();
|
||||
@@ -2582,7 +2621,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
|
||||
|
||||
#ifdef DISPLAY_SURFACE_WINDOW
|
||||
if (isVisible() && g_emu_thread->shouldRenderToMain())
|
||||
m_display_surface->setPosition(screen()->availableGeometry().topLeft());
|
||||
m_display_surface->setGeometry(screen()->geometry());
|
||||
else
|
||||
restoreDisplayWindowGeometryFromConfig();
|
||||
|
||||
@@ -2709,6 +2748,7 @@ void MainWindow::releaseRenderWindow()
|
||||
// Now we can safely destroy the display window.
|
||||
destroyDisplayWidget(true);
|
||||
m_display_created = false;
|
||||
m_display_is_exclusive_fullscreen = false;
|
||||
|
||||
m_ui.actionViewSystemDisplay->setEnabled(false);
|
||||
m_ui.actionFullscreen->setEnabled(false);
|
||||
@@ -2719,7 +2759,7 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
|
||||
if (!m_display_surface)
|
||||
return;
|
||||
|
||||
if (!m_display_surface->isFullScreen() && !isRenderingToMain())
|
||||
if (!(m_display_surface->isFullScreen() || m_display_is_exclusive_fullscreen) && !isRenderingToMain())
|
||||
saveDisplayWindowGeometryToConfig();
|
||||
|
||||
if (isRenderingToMain())
|
||||
|
||||
@@ -317,6 +317,7 @@ private:
|
||||
QMenu* m_settings_toolbar_menu = nullptr;
|
||||
|
||||
bool m_display_created = false;
|
||||
bool m_display_is_exclusive_fullscreen = false;
|
||||
bool m_relative_mouse_mode = false;
|
||||
bool m_hide_mouse_cursor = false;
|
||||
bool m_was_paused_on_surface_loss = false;
|
||||
|
||||
+9
-2
@@ -2086,7 +2086,8 @@ void QtHost::PrintCommandLineHelp(const std::string_view progname)
|
||||
std::fprintf(stderr, " -version: Displays version information and exits.\n");
|
||||
std::fprintf(stderr, " -batch: Enables batch mode (exits after shutting down).\n");
|
||||
std::fprintf(stderr, " -nogui: Hides main window while running (implies batch mode).\n");
|
||||
std::fprintf(stderr, " -portable: Force enable portable mode to store data in local PCSX2 path instead of the default configuration path.\n");
|
||||
std::fprintf(stderr, " -portable: Force enable portable mode to store data in local PCSX2 path instead of the default configuration path. Overrides '-datapath'.\n");
|
||||
std::fprintf(stderr, " -datapath <path>: Specify the directory to be used for all application data.\n");
|
||||
std::fprintf(stderr, " -elf <file>: Overrides the boot ELF with the specified filename.\n");
|
||||
std::fprintf(stderr, " -gameargs <string>: passes the specified quoted space-delimited string of launch arguments.\n");
|
||||
std::fprintf(stderr, " -disc <path>: Uses the specified host DVD drive as a source.\n");
|
||||
@@ -2165,6 +2166,12 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
EmuConfig.IsPortableMode = true;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM(QStringLiteral("-datapath")))
|
||||
{
|
||||
std::string path = (++it)->toStdString();
|
||||
EmuConfig.CustomDataPath = path;
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG(QStringLiteral("-fastboot")))
|
||||
{
|
||||
AutoBoot(autoboot)->fast_boot = true;
|
||||
@@ -2297,7 +2304,7 @@ bool QtHost::ParseCommandLineOptions(const QStringList& args, std::shared_ptr<VM
|
||||
Console.Warning("Skipping autoboot due to no boot parameters.");
|
||||
autoboot.reset();
|
||||
}
|
||||
|
||||
|
||||
if(autoboot && autoboot->start_turbo.value_or(false) && autoboot->start_unlimited.value_or(false))
|
||||
{
|
||||
Console.Warning("Both turbo and unlimited frame limit modes requested. Using unlimited.");
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QtWidgets/QSpinBox>
|
||||
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/HostSys.h"
|
||||
#include "common/Path.h"
|
||||
|
||||
#include "pcsx2/Config.h"
|
||||
@@ -1261,8 +1262,8 @@ namespace SettingWidgetBinder
|
||||
widget->connect(widget, &QLineEdit::editingFinished, widget, std::move(value_changed));
|
||||
}
|
||||
|
||||
static inline void BindWidgetToFileSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
|
||||
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
|
||||
static inline void BindWidgetToAudioFileSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
|
||||
QAbstractButton* preview_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
|
||||
const QString& filter, bool allow_pergame = false, bool use_relative = true)
|
||||
{
|
||||
using Accessor = SettingAccessor<QLineEdit>;
|
||||
@@ -1296,40 +1297,25 @@ namespace SettingWidgetBinder
|
||||
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), relative_path.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::SetBaseStringSettingValue(section.c_str(), key.c_str(), new_value.c_str());
|
||||
}
|
||||
|
||||
if (!FileSystem::FileExists(new_value.c_str()))
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"),
|
||||
qApp->translate("SettingWidgetBinder", "File cannot be found."));
|
||||
}
|
||||
|
||||
Host::CommitBaseSettingChanges();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(QtUtils::GetRootWidget(widget), qApp->translate("SettingWidgetBinder", "Error"),
|
||||
qApp->translate("SettingWidgetBinder", "File path cannot be empty."));
|
||||
}
|
||||
Host::RemoveBaseSettingValue(section.c_str(), key.c_str());
|
||||
|
||||
// reset to old value
|
||||
std::string current_path(Host::GetBaseStringSettingValue(section.c_str(), key.c_str(), default_value.c_str()));
|
||||
if (current_path.empty())
|
||||
current_path = default_value;
|
||||
else if (use_relative && !Path::IsAbsolute(current_path))
|
||||
current_path = Path::Canonicalize(Path::Combine(EmuFolders::DataRoot, current_path));
|
||||
|
||||
widget->setText(QString::fromStdString(current_path));
|
||||
};
|
||||
|
||||
if (browse_button)
|
||||
{
|
||||
QObject::connect(browse_button, &QAbstractButton::clicked, browse_button, [widget, key, value_changed, filter]() {
|
||||
const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(QtUtils::GetRootWidget(widget),
|
||||
qApp->translate("SettingWidgetBinder", "Select File"), QString(), filter)));
|
||||
qApp->translate("SettingWidgetBinder", "Select Audio File"), QString(), filter)));
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
@@ -1337,19 +1323,22 @@ namespace SettingWidgetBinder
|
||||
value_changed();
|
||||
});
|
||||
}
|
||||
if (open_button)
|
||||
if (preview_button)
|
||||
{
|
||||
QObject::connect(open_button, &QAbstractButton::clicked, open_button, [widget]() {
|
||||
QString path(Accessor::getStringValue(widget));
|
||||
if (!path.isEmpty())
|
||||
QtUtils::OpenURL(QtUtils::GetRootWidget(widget), QUrl::fromLocalFile(path));
|
||||
QObject::connect(preview_button, &QAbstractButton::clicked, preview_button, [widget, default_value = std::move(default_value)]() {
|
||||
const QByteArray path = widget->text().toUtf8();
|
||||
Common::PlaySoundAsync(
|
||||
(path.isEmpty()
|
||||
? default_value
|
||||
: path.constData()).c_str()
|
||||
);
|
||||
});
|
||||
}
|
||||
if (reset_button)
|
||||
{
|
||||
QObject::connect(
|
||||
reset_button, &QAbstractButton::clicked, reset_button, [widget, default_value = std::move(default_value), value_changed]() {
|
||||
widget->setText(QString::fromStdString(default_value));
|
||||
reset_button, &QAbstractButton::clicked, reset_button, [widget, value_changed]() {
|
||||
widget->clear();
|
||||
value_changed();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
const char* AchievementSettingsWidget::AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("AchievementSettingsWidget", "WAV Audio Files (*.wav)");
|
||||
static constexpr const char* AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("AchievementSettingsWidget", "WAV Audio Files (*.wav)");
|
||||
static constexpr const char* DEFAULT_INFO_SOUND_NAME = "sounds/achievements/message.wav";
|
||||
static constexpr const char* DEFAULT_UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav";
|
||||
static constexpr const char* DEFAULT_LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav";
|
||||
|
||||
AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
|
||||
: SettingsWidget(settings_dialog, parent)
|
||||
@@ -44,9 +47,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_di
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.achievementNotificationsDuration, "Achievements", "NotificationsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.leaderboardNotificationsDuration, "Achievements", "LeaderboardsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION);
|
||||
|
||||
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
SettingWidgetBinder::BindWidgetToAudioFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, DEFAULT_INFO_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
SettingWidgetBinder::BindWidgetToAudioFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, DEFAULT_UNLOCK_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
SettingWidgetBinder::BindWidgetToAudioFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, DEFAULT_LBSUBMIT_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
|
||||
|
||||
dialog()->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on startup."));
|
||||
dialog()->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."));
|
||||
@@ -117,6 +120,10 @@ void AchievementSettingsWidget::updateEnableState()
|
||||
const bool info = enabled && sound && dialog()->getEffectiveBoolValue("Achievements", "InfoSound", true);
|
||||
const bool unlock = enabled && sound && dialog()->getEffectiveBoolValue("Achievements", "UnlockSound", true);
|
||||
const bool lbsound = enabled && sound && dialog()->getEffectiveBoolValue("Achievements", "LBSubmitSound", true);
|
||||
|
||||
m_ui.viewProfile->setEnabled(enabled);
|
||||
m_ui.loginButton->setEnabled(enabled);
|
||||
|
||||
m_ui.hardcoreMode->setEnabled(enabled);
|
||||
m_ui.achievementNotifications->setEnabled(enabled);
|
||||
m_ui.leaderboardNotifications->setEnabled(enabled);
|
||||
|
||||
@@ -26,7 +26,6 @@ private Q_SLOTS:
|
||||
|
||||
private:
|
||||
void updateLoginState();
|
||||
static const char* AUDIO_FILE_FILTER;
|
||||
|
||||
Ui::AchievementSettingsWidget m_ui;
|
||||
};
|
||||
|
||||
@@ -11,13 +11,118 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="loginBox">
|
||||
<property name="title">
|
||||
<string>Account</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="acccountLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
<string>Enable Achievements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="loginStatus">
|
||||
<property name="text">
|
||||
<string>Username:
|
||||
Login token generated at:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>viewProfile</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="achievementButtons">
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewProfile">
|
||||
<property name="text">
|
||||
<string>View Profile...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Login...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameInfoBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Game Info</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gameInfo">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>viewProfile</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rcheevosDisclaimer">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/">retroachievements.org</a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::TextFormat::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>enable</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="settingsBox">
|
||||
<property name="title">
|
||||
<string>Settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_5">
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="unofficialAchievements">
|
||||
<property name="text">
|
||||
<string>Test Unofficial Achievements</string>
|
||||
@@ -25,23 +130,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="hardcoreMode">
|
||||
<widget class="QCheckBox" name="spectatorMode">
|
||||
<property name="text">
|
||||
<string>Enable Hardcore Mode</string>
|
||||
<string>Enable Spectator Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<widget class="QCheckBox" name="hardcoreMode">
|
||||
<property name="text">
|
||||
<string>Enable Achievements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="spectatorMode">
|
||||
<property name="text">
|
||||
<string>Enable Spectator Mode</string>
|
||||
<string>Enable Hardcore Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -316,10 +414,18 @@
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_6">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLineEdit" name="lbSoundPath"/>
|
||||
<widget class="QLineEdit" name="lbSoundPath">
|
||||
<property name="placeholderText">
|
||||
<string>Default Sound Effect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLineEdit" name="notificationSoundPath"/>
|
||||
<widget class="QLineEdit" name="notificationSoundPath">
|
||||
<property name="placeholderText">
|
||||
<string>Default Sound Effect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="unlockSoundBrowse">
|
||||
@@ -350,7 +456,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLineEdit" name="unlockSoundPath"/>
|
||||
<widget class="QLineEdit" name="unlockSoundPath">
|
||||
<property name="placeholderText">
|
||||
<string>Default Sound Effect</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="lbSoundBrowse">
|
||||
@@ -411,94 +521,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="loginBox">
|
||||
<property name="title">
|
||||
<string>Account</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<item>
|
||||
<widget class="QLabel" name="loginStatus">
|
||||
<property name="text">
|
||||
<string>Username:
|
||||
Login token generated at:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>viewProfile</cstring>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="achievementButtons">
|
||||
<item>
|
||||
<widget class="QPushButton" name="viewProfile">
|
||||
<property name="text">
|
||||
<string>View Profile...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="loginButton">
|
||||
<property name="text">
|
||||
<string>Login...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gameInfoBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>75</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Game Info</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="gameInfo">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rcheevosDisclaimer">
|
||||
<property name="text">
|
||||
<string><html><head/><body><p align="justify">PCSX2 uses RetroAchievements as an achievement database and for tracking progress. To use achievements, please sign up for an account at <a href="https://retroachievements.org/">retroachievements.org</a>.</p><p align="justify">To view the achievement list in-game, press the hotkey for <span style=" font-weight:600;">Open Pause Menu</span> and select <span style=" font-weight:600;">Achievements</span> from the menu.</p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::TextFormat::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
@@ -516,9 +538,11 @@ Login token generated at:</string>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>enable</tabstop>
|
||||
<tabstop>viewProfile</tabstop>
|
||||
<tabstop>loginButton</tabstop>
|
||||
<tabstop>hardcoreMode</tabstop>
|
||||
<tabstop>encoreMode</tabstop>
|
||||
<tabstop>spectatorMode</tabstop>
|
||||
<tabstop>encoreMode</tabstop>
|
||||
<tabstop>unofficialAchievements</tabstop>
|
||||
<tabstop>achievementNotifications</tabstop>
|
||||
<tabstop>achievementNotificationsDuration</tabstop>
|
||||
@@ -544,8 +568,6 @@ Login token generated at:</string>
|
||||
<tabstop>lbSoundBrowse</tabstop>
|
||||
<tabstop>lbSoundOpen</tabstop>
|
||||
<tabstop>lbSoundReset</tabstop>
|
||||
<tabstop>viewProfile</tabstop>
|
||||
<tabstop>loginButton</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../resources/resources.qrc"/>
|
||||
|
||||
@@ -70,23 +70,9 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* settings_dialog
|
||||
std::clamp(Host::GetBaseIntSettingValue("EmuCore/Speedhacks", "EECycleRate", DEFAULT_EE_CYCLE_RATE) - MINIMUM_EE_CYCLE_RATE,
|
||||
0, MAXIMUM_EE_CYCLE_RATE - MINIMUM_EE_CYCLE_RATE))));
|
||||
|
||||
// Disable cheats, use the cheats panel instead (move fastcvd up in its spot).
|
||||
const int count = m_ui.systemSettingsLayout->count();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
QLayoutItem* item = m_ui.systemSettingsLayout->itemAt(i);
|
||||
if (item && item->widget() == m_ui.cheats)
|
||||
{
|
||||
int row, col, rowSpan, colSpan;
|
||||
m_ui.systemSettingsLayout->getItemPosition(i, &row, &col, &rowSpan, &colSpan);
|
||||
delete m_ui.systemSettingsLayout->takeAt(i);
|
||||
m_ui.systemSettingsLayout->removeWidget(m_ui.fastCDVD);
|
||||
m_ui.systemSettingsLayout->addWidget(m_ui.fastCDVD, row, col);
|
||||
delete m_ui.cheats;
|
||||
m_ui.cheats = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Disable cheats, use the cheats panel instead
|
||||
m_ui.systemSettingsLayout->removeWidget(m_ui.cheats);
|
||||
m_ui.cheats->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -96,9 +82,11 @@ EmulationSettingsWidget::EmulationSettingsWidget(SettingsWindow* settings_dialog
|
||||
|
||||
// Allow for FastCDVD for per-game settings only
|
||||
m_ui.systemSettingsLayout->removeWidget(m_ui.fastCDVD);
|
||||
m_ui.fastCDVD->deleteLater();
|
||||
m_ui.fastCDVD->hide();
|
||||
}
|
||||
|
||||
reflowCheckBoxes(m_ui.systemSettingsLayout);
|
||||
|
||||
const std::optional<int> cycle_rate =
|
||||
dialog()->getIntValue("EmuCore/Speedhacks", "EECycleRate", sif ? std::nullopt : std::optional<int>(DEFAULT_EE_CYCLE_RATE));
|
||||
m_ui.eeCycleRate->setCurrentIndex(cycle_rate.has_value() ? (std::clamp(cycle_rate.value(), MINIMUM_EE_CYCLE_RATE, MAXIMUM_EE_CYCLE_RATE) + (0 - MINIMUM_EE_CYCLE_RATE) + static_cast<int>(dialog()->isPerGameSettings())) : 0);
|
||||
|
||||
@@ -270,7 +270,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="hwFixesLayout">
|
||||
<layout class="QGridLayout" name="hwFixesLayout">
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="drawBuffering">
|
||||
<property name="text">
|
||||
<string>Draw Buffering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="estimateTextureRegion">
|
||||
<property name="text">
|
||||
|
||||
@@ -65,6 +65,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="hwAA1">
|
||||
<property name="text">
|
||||
<string>AA1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="enableHWFixes">
|
||||
<property name="text">
|
||||
<string>Manual Hardware Renderer Fixes</string>
|
||||
@@ -235,6 +242,7 @@
|
||||
<tabstop>blending</tabstop>
|
||||
<tabstop>mipmapping</tabstop>
|
||||
<tabstop>accurateAlphaTest</tabstop>
|
||||
<tabstop>hwAA1</tabstop>
|
||||
<tabstop>enableHWFixes</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
|
||||
@@ -112,6 +112,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_hw.dithering, "EmuCore/GS", "dithering_ps2", 2);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.mipmapping, "EmuCore/GS", "hw_mipmap", true);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.accurateAlphaTest, "EmuCore/GS", "HWAccurateAlphaTest", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.hwAA1, "EmuCore/GS", "HWAA1", false);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(
|
||||
sif, m_hw.blending, "EmuCore/GS", "accurate_blending_unit", static_cast<int>(AccBlendLevel::Basic));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_hw.enableHWFixes, "EmuCore/GS", "UserHacks", false);
|
||||
@@ -152,6 +153,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
sif, m_fixes.limit24BitDepth, "EmuCore/GS", "UserHacks_Limit24BitDepth", static_cast<int>(GSLimit24BitDepth::Disabled));
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.readTCOnClose, "EmuCore/GS", "UserHacks_ReadTCOnClose", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.estimateTextureRegion, "EmuCore/GS", "UserHacks_EstimateTextureRegion", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.drawBuffering, "EmuCore/GS", "UserHacks_DrawBuffering", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_fixes.gpuPaletteConversion, "EmuCore/GS", "paltex", false);
|
||||
connect(m_fixes.cpuSpriteRenderBW, &QComboBox::currentIndexChanged, this,
|
||||
&GraphicsSettingsWidget::onCPUSpriteRenderBWChanged);
|
||||
@@ -491,6 +493,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
dialog()->registerWidgetHelp(
|
||||
m_hw.accurateAlphaTest, tr("Accurate Alpha Test"), tr("Unchecked"), tr("Enables accurate alpha testing, which some games require to render correctly. This may require more draw calls and result in a speed penalty."));
|
||||
|
||||
dialog()->registerWidgetHelp(
|
||||
m_hw.hwAA1, tr("AA1"), tr("Unchecked"), tr("Enables AA1 (PS2 antialiasing), which some games require to render correctly. This may result in a heavy performance penalty."));
|
||||
|
||||
dialog()->registerWidgetHelp(
|
||||
m_hw.textureFiltering, tr("Texture Filtering"), tr("Bilinear (PS2)"),
|
||||
tr("Changes what filtering algorithm is used to map textures to surfaces.<br> "
|
||||
@@ -610,6 +615,9 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
|
||||
|
||||
dialog()->registerWidgetHelp(m_fixes.estimateTextureRegion, tr("Estimate Texture Region"), tr("Unchecked"),
|
||||
tr("Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games)."));
|
||||
|
||||
dialog()->registerWidgetHelp(m_fixes.drawBuffering, tr("Draw Buffering"), tr("Unchecked"),
|
||||
tr("Attempts to reduce draw calls in games which do heavy context switching for blending purposes."));
|
||||
}
|
||||
|
||||
// Upscaling Fixes tab
|
||||
|
||||
@@ -184,10 +184,6 @@ void OSDSettingsWidget::onBrowseOsdFontPathClicked()
|
||||
saveOsdFontPathSetting(QDir::toNativeSeparators(selected));
|
||||
});
|
||||
|
||||
connect(m_font_picker, &QObject::destroyed, this, [this]() {
|
||||
m_font_picker = nullptr;
|
||||
});
|
||||
|
||||
m_font_picker->show();
|
||||
}
|
||||
|
||||
@@ -313,4 +309,4 @@ void OSDSettingsWidget::onDeselectAllClicked()
|
||||
m_ui.warnAboutUnsafeSettings->setChecked(true);
|
||||
}
|
||||
|
||||
#include "moc_OSDSettingsWidget.cpp"
|
||||
#include "moc_OSDSettingsWidget.cpp"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "SettingsWidget.h"
|
||||
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QScrollArea>
|
||||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QTabBar>
|
||||
@@ -115,4 +116,29 @@ void SettingsWidget::updateTabMargins(QScrollArea* scroll_area)
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsWidget::reflowCheckBoxes(QGridLayout* layout)
|
||||
{
|
||||
std::vector<QWidget*> widgets;
|
||||
|
||||
for (int row = 0; row < layout->rowCount(); row++)
|
||||
{
|
||||
for (int column = 0; column < layout->columnCount(); column++)
|
||||
{
|
||||
QLayoutItem* item = layout->itemAtPosition(row, column);
|
||||
if (item && item->widget())
|
||||
{
|
||||
widgets.push_back(item->widget());
|
||||
layout->removeWidget(item->widget());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < widgets.size(); i++)
|
||||
{
|
||||
int row = static_cast<int>(i / 2);
|
||||
int column = static_cast<int>(i % 2);
|
||||
layout->addWidget(widgets[i], row, column);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_SettingsWidget.cpp"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class QGridLayout;
|
||||
class QScrollArea;
|
||||
class QTabWidget;
|
||||
|
||||
@@ -45,6 +46,10 @@ protected:
|
||||
QWidget* addTab(QString name, QWidget* contents, bool custom_margins = false);
|
||||
void setTabVisible(QWidget* tab, bool is_visible, QWidget* switch_to = nullptr);
|
||||
|
||||
// Rearrange the checkboxes in a group so that there aren't any gaps. To be
|
||||
// used if checkboxes have been hidden programmatically.
|
||||
void reflowCheckBoxes(QGridLayout* layout);
|
||||
|
||||
private:
|
||||
void updateTabMargins(QScrollArea* scroll_area);
|
||||
|
||||
|
||||
@@ -14,16 +14,10 @@
|
||||
#include "VMManager.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include "common/RedtapeWindows.h"
|
||||
#include "common/RedtapeWilCom.h"
|
||||
#include <shlobj.h>
|
||||
#include <winnls.h>
|
||||
#include <shobjidl.h>
|
||||
#include <objbase.h>
|
||||
#include <objidl.h>
|
||||
#include <shlguid.h>
|
||||
#include <comdef.h>
|
||||
|
||||
#include <wrl/client.h>
|
||||
#endif
|
||||
|
||||
ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& title, const QString& path)
|
||||
@@ -41,10 +35,10 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
|
||||
m_ui.shortcutStartMenu->setText(tr("Application Launcher"));
|
||||
#endif
|
||||
|
||||
QButtonGroup* buttonGroup = new QButtonGroup(this);
|
||||
buttonGroup->setExclusive(true);
|
||||
buttonGroup->addButton(m_ui.fastForwardTurboOption);
|
||||
buttonGroup->addButton(m_ui.fastForwardUnlimitedOption);
|
||||
QButtonGroup* speedGroup = new QButtonGroup(this);
|
||||
speedGroup->setExclusive(true);
|
||||
speedGroup->addButton(m_ui.fastForwardTurboOption);
|
||||
speedGroup->addButton(m_ui.fastForwardUnlimitedOption);
|
||||
m_ui.fastForwardTurboOption->setChecked(true);
|
||||
|
||||
connect(m_ui.overrideBootELFButton, &QPushButton::clicked, [&]() {
|
||||
@@ -75,6 +69,37 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
|
||||
|
||||
m_ui.loadStateIndex->setMaximum(VMManager::NUM_SAVE_STATE_SLOTS);
|
||||
|
||||
m_ui.iconPreview->setPixmap(QtHost::GetAppIcon().pixmap(m_ui.iconPreview->size()));
|
||||
m_ui.resetIconButton->setEnabled(false);
|
||||
|
||||
connect(m_ui.browseIconButton, &QPushButton::clicked, [&]() {
|
||||
#if defined(_WIN32)
|
||||
const QString filter = tr("Icon Files (*.ico);;All Files (*.*)");
|
||||
#else
|
||||
const QString filter = tr("Image Files (*.png *.jpg *.svg *.webp);;All Files (*.*)");
|
||||
#endif
|
||||
const QString icon_file = QFileDialog::getOpenFileName(this, tr("Select Icon"), QString(), filter);
|
||||
if (!icon_file.isEmpty())
|
||||
{
|
||||
QPixmap pixmap(icon_file);
|
||||
if (pixmap.isNull())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Invalid Icon"), tr("The selected file could not be loaded as an icon."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui.iconPath->setText(Path::ToNativePath(icon_file.toStdString()).c_str());
|
||||
m_ui.iconPreview->setPixmap(pixmap.scaled(m_ui.iconPreview->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
m_ui.resetIconButton->setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_ui.resetIconButton, &QPushButton::clicked, [&]() {
|
||||
m_ui.iconPath->clear();
|
||||
m_ui.iconPreview->setPixmap(QtHost::GetAppIcon().pixmap(m_ui.iconPreview->size()));
|
||||
m_ui.resetIconButton->setEnabled(false);
|
||||
});
|
||||
|
||||
if (std::getenv("container"))
|
||||
{
|
||||
m_ui.portableModeToggle->setEnabled(false);
|
||||
@@ -107,7 +132,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
|
||||
if (m_ui.loadStateIndexToggle->isChecked())
|
||||
{
|
||||
const s32 load_state_index = m_ui.loadStateIndex->value();
|
||||
if (load_state_index >= 1 && load_state_index <= VMManager::NUM_SAVE_STATE_SLOTS)
|
||||
if (load_state_index > 0 && load_state_index <= VMManager::NUM_SAVE_STATE_SLOTS)
|
||||
{
|
||||
args.push_back("-state");
|
||||
args.push_back(StringUtil::ToChars(load_state_index));
|
||||
@@ -135,14 +160,15 @@ 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, m_ui.shortcutDesktop->isChecked());
|
||||
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), 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, bool is_desktop)
|
||||
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())
|
||||
@@ -165,10 +191,9 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
// 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 (PWSTR directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
|
||||
if (wil::unique_cotaskmem_string directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
|
||||
{
|
||||
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory);
|
||||
CoTaskMemFree(directory);
|
||||
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory.get());
|
||||
|
||||
if (is_desktop)
|
||||
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
|
||||
@@ -186,7 +211,6 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
}
|
||||
else
|
||||
{
|
||||
CoTaskMemFree(directory);
|
||||
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;
|
||||
}
|
||||
@@ -205,7 +229,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
|
||||
if (!lossless)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,23 +255,19 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
return;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IShellLink> pShellLink;
|
||||
Microsoft::WRL::ComPtr<IPersistFile> pPersistFile;
|
||||
wil::unique_couninitialize_call co_cleanup;
|
||||
|
||||
const auto cleanup = [&](bool return_value, const QString& fail_reason) -> bool {
|
||||
if (!return_value)
|
||||
{
|
||||
Console.ErrorFmt("Failed to create shortcut: {}", fail_reason.toStdString());
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), fail_reason, QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
}
|
||||
CoUninitialize();
|
||||
return return_value;
|
||||
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);
|
||||
};
|
||||
|
||||
res = CoCreateInstance(__uuidof(ShellLink), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink));
|
||||
if (FAILED(res))
|
||||
wil::com_ptr_nothrow<IShellLink> pShellLink = wil::CoCreateInstanceNoThrow<IShellLink>(CLSID_ShellLink);
|
||||
wil::com_ptr_nothrow<IPersistFile> pPersistFile;
|
||||
|
||||
if (!pShellLink)
|
||||
{
|
||||
cleanup(false, tr("CoCreateInstance failed"));
|
||||
report_error(tr("CoCreateInstance failed"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -256,7 +276,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
res = pShellLink->SetPath(target_file.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetPath failed (%1)").arg(str_error(res)));
|
||||
report_error(tr("SetPath failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +285,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
res = pShellLink->SetWorkingDirectory(working_dir.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetWorkingDirectory failed (%1)").arg(str_error(res)));
|
||||
report_error(tr("SetWorkingDirectory failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -276,26 +296,40 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
res = pShellLink->SetArguments(target_cli_args.c_str());
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("SetArguments failed (%1)").arg(str_error(res)));
|
||||
report_error(tr("SetArguments failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the icon
|
||||
std::string icon_path = Path::ToNativePath(Path::Combine(Path::GetDirectory(FileSystem::GetProgramPath()), "resources/icons/AppIconLarge.ico"));
|
||||
const std::wstring w_icon_path = StringUtil::UTF8StringToWideString(icon_path);
|
||||
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))
|
||||
{
|
||||
cleanup(false, tr("SetIconLocation failed (%1)").arg(str_error(res)));
|
||||
report_error(tr("SetIconLocation failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the IPersistFile object to save the shell link
|
||||
res = pShellLink.As(&pPersistFile);
|
||||
res = pShellLink.query_to(&pPersistFile);
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("QueryInterface failed (%1)").arg(str_error(res)));
|
||||
report_error(tr("QueryInterface failed (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -304,12 +338,10 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
res = pPersistFile->Save(w_link_file.c_str(), TRUE);
|
||||
if (FAILED(res))
|
||||
{
|
||||
cleanup(false, tr("Failed to save the shortcut (%1)").arg(str_error(res)));
|
||||
report_error(tr("Failed to save the shortcut (%1)").arg(str_error(res)));
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup(true, {});
|
||||
|
||||
#else
|
||||
|
||||
if (name.empty())
|
||||
@@ -365,25 +397,38 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
std::string icon_name;
|
||||
if (is_flatpak) // Flatpak
|
||||
if (!icon_path.empty())
|
||||
{
|
||||
executable_path = "flatpak run net.pcsx2.PCSX2";
|
||||
icon_name = "net.pcsx2.PCSX2";
|
||||
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
|
||||
{
|
||||
icon_name = "PCSX2";
|
||||
std::string icon_path = fmt::format("{}/{}.png", icon_dest, icon_name).c_str();
|
||||
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
|
||||
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
|
||||
// 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
|
||||
@@ -393,7 +438,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
|
||||
|
||||
if (!lossless)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
~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, bool is_desktop);
|
||||
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.
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>700</height>
|
||||
<height>825</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="shortcutLayout">
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="6" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="launchArgsGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
@@ -291,7 +291,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QDialogButtonBox" name="dialogButtons">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
@@ -301,7 +301,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="instruction">
|
||||
<property name="text">
|
||||
<string>Launch Options</string>
|
||||
@@ -334,7 +334,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="7" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
@@ -347,11 +347,100 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="iconLabel">
|
||||
<property name="text">
|
||||
<string>Icon</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>browseIconButton</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="iconGroup">
|
||||
<property name="title">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="iconLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="resetIconButton">
|
||||
<property name="toolTip">
|
||||
<string>Reset to default application icon</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="browseIconButton">
|
||||
<property name="text">
|
||||
<string>Browse...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QLabel" name="iconPreview">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::ContextMenuPolicy::NoContextMenu</enum>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="scaledContents">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::NoTextInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>iconPath</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="iconPath">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Default PCSX2 Icon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>shortcutDesktop</tabstop>
|
||||
<tabstop>shortcutStartMenu</tabstop>
|
||||
<tabstop>iconPath</tabstop>
|
||||
<tabstop>browseIconButton</tabstop>
|
||||
<tabstop>resetIconButton</tabstop>
|
||||
<tabstop>portableModeToggle</tabstop>
|
||||
<tabstop>overrideBootELFToggle</tabstop>
|
||||
<tabstop>overrideBootELFPath</tabstop>
|
||||
@@ -360,6 +449,9 @@
|
||||
<tabstop>gameArgs</tabstop>
|
||||
<tabstop>bootOptionToggle</tabstop>
|
||||
<tabstop>bootOptionDropdown</tabstop>
|
||||
<tabstop>fastForwardOptionToggle</tabstop>
|
||||
<tabstop>fastForwardTurboOption</tabstop>
|
||||
<tabstop>fastForwardUnlimitedOption</tabstop>
|
||||
<tabstop>fullscreenMode</tabstop>
|
||||
<tabstop>fullscreenModeDropdown</tabstop>
|
||||
<tabstop>bigPictureModeToggle</tabstop>
|
||||
|
||||
+3247
-3006
File diff suppressed because it is too large
Load Diff
@@ -39,6 +39,7 @@
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)\SDL3</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)\kddockwidgets-qt6</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\fmt\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\winwil\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\lzma\include</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\simpleini\include</AdditionalIncludeDirectories>
|
||||
|
||||
+112
-61
@@ -63,6 +63,10 @@ namespace Achievements
|
||||
static constexpr float LEADERBOARD_STARTED_NOTIFICATION_TIME = 3.0f;
|
||||
static constexpr float LEADERBOARD_FAILED_NOTIFICATION_TIME = 3.0f;
|
||||
|
||||
static constexpr const char* DEFAULT_INFO_SOUND_NAME = "sounds/achievements/message.wav";
|
||||
static constexpr const char* DEFAULT_UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav";
|
||||
static constexpr const char* DEFAULT_LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav";
|
||||
|
||||
static constexpr float INDICATOR_FADE_IN_TIME = 0.1f;
|
||||
static constexpr float INDICATOR_FADE_OUT_TIME = 0.5f;
|
||||
|
||||
@@ -455,9 +459,9 @@ bool Achievements::Initialize()
|
||||
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
|
||||
secretsInterface->SetStringValue("Achievements", "Token", oldToken.c_str());
|
||||
secretsInterface->Save();
|
||||
|
||||
|
||||
oldToken.clear();
|
||||
|
||||
|
||||
auto baseLock = Host::GetSettingsLock();
|
||||
SettingsInterface* baseInterface = Host::Internal::GetBaseSettingsLayer();
|
||||
baseInterface->DeleteValue("Achievements", "Token");
|
||||
@@ -540,7 +544,7 @@ void Achievements::UpdateNotificationPosition()
|
||||
{
|
||||
// Set notification position based on achievement settings
|
||||
float horizontal_position, vertical_position, direction;
|
||||
|
||||
|
||||
// Determine horizontal alignment
|
||||
switch (EmuConfig.Achievements.NotificationPosition)
|
||||
{
|
||||
@@ -549,13 +553,13 @@ void Achievements::UpdateNotificationPosition()
|
||||
case OsdOverlayPos::BottomLeft:
|
||||
horizontal_position = 0.0f; // Left
|
||||
break;
|
||||
|
||||
|
||||
case OsdOverlayPos::TopCenter:
|
||||
case OsdOverlayPos::Center:
|
||||
case OsdOverlayPos::BottomCenter:
|
||||
horizontal_position = 0.5f; // Center
|
||||
break;
|
||||
|
||||
|
||||
case OsdOverlayPos::TopRight:
|
||||
case OsdOverlayPos::CenterRight:
|
||||
case OsdOverlayPos::BottomRight:
|
||||
@@ -563,7 +567,7 @@ void Achievements::UpdateNotificationPosition()
|
||||
horizontal_position = 1.0f; // Right
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Determine vertical alignment and stacking direction
|
||||
switch (EmuConfig.Achievements.NotificationPosition)
|
||||
{
|
||||
@@ -573,14 +577,14 @@ void Achievements::UpdateNotificationPosition()
|
||||
vertical_position = 0.15f; // Top area
|
||||
direction = 1.0f; // Stack downward
|
||||
break;
|
||||
|
||||
|
||||
case OsdOverlayPos::CenterLeft:
|
||||
case OsdOverlayPos::Center:
|
||||
case OsdOverlayPos::CenterRight:
|
||||
vertical_position = 0.5f; // Center
|
||||
direction = 1.0f; // Stack downward
|
||||
break;
|
||||
|
||||
|
||||
case OsdOverlayPos::BottomLeft:
|
||||
case OsdOverlayPos::BottomCenter:
|
||||
case OsdOverlayPos::BottomRight:
|
||||
@@ -589,7 +593,7 @@ void Achievements::UpdateNotificationPosition()
|
||||
direction = -1.0f; // Stack upward
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
ImGuiFullscreen::SetNotificationPosition(horizontal_position, vertical_position, direction);
|
||||
}
|
||||
|
||||
@@ -1142,7 +1146,11 @@ void Achievements::DisplayAchievementSummary()
|
||||
}
|
||||
|
||||
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.InfoSound)
|
||||
Common::PlaySoundAsync(EmuConfig.Achievements.InfoSoundName.c_str());
|
||||
Common::PlaySoundAsync(
|
||||
(EmuConfig.Achievements.InfoSoundName.empty()
|
||||
? Path::Combine(EmuFolders::Resources, DEFAULT_INFO_SOUND_NAME)
|
||||
: EmuConfig.Achievements.InfoSoundName).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void Achievements::DisplayHardcoreDeferredMessage()
|
||||
@@ -1194,7 +1202,11 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
|
||||
}
|
||||
|
||||
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.UnlockSound)
|
||||
Common::PlaySoundAsync(EmuConfig.Achievements.UnlockSoundName.c_str());
|
||||
Common::PlaySoundAsync(
|
||||
(EmuConfig.Achievements.UnlockSoundName.empty()
|
||||
? Path::Combine(EmuFolders::Resources, DEFAULT_UNLOCK_SOUND_NAME)
|
||||
: EmuConfig.Achievements.UnlockSoundName).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
|
||||
@@ -1315,7 +1327,11 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
|
||||
}
|
||||
|
||||
if (EmuConfig.Achievements.SoundEffects && EmuConfig.Achievements.LBSubmitSound)
|
||||
Common::PlaySoundAsync(EmuConfig.Achievements.LBSubmitSoundName.c_str());
|
||||
Common::PlaySoundAsync(
|
||||
(EmuConfig.Achievements.LBSubmitSoundName.empty()
|
||||
? Path::Combine(EmuFolders::Resources, DEFAULT_LBSUBMIT_SOUND_NAME)
|
||||
: EmuConfig.Achievements.LBSubmitSoundName).c_str()
|
||||
);
|
||||
}
|
||||
|
||||
void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* event)
|
||||
@@ -1524,6 +1540,11 @@ void Achievements::DisableHardcoreMode()
|
||||
if (RA_HardcoreModeIsActive())
|
||||
RA_DisableHardcore();
|
||||
|
||||
if (s_hardcore_mode && !RA_HardcoreModeIsActive())
|
||||
{
|
||||
s_hardcore_mode = false;
|
||||
Host::OnAchievementsHardcoreModeChanged(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -1537,6 +1558,18 @@ bool Achievements::ResetHardcoreMode(bool is_booting)
|
||||
if (!IsActive())
|
||||
return false;
|
||||
|
||||
#ifdef ENABLE_RAINTEGRATION
|
||||
if (IsUsingRAIntegration())
|
||||
{
|
||||
const bool ra_hardcore = (RA_HardcoreModeIsActive() != 0);
|
||||
if (ra_hardcore == s_hardcore_mode)
|
||||
return false;
|
||||
s_hardcore_mode = ra_hardcore;
|
||||
Host::OnAchievementsHardcoreModeChanged(ra_hardcore);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const auto lock = GetLock();
|
||||
|
||||
// If we're not logged in, don't apply hardcore mode restrictions.
|
||||
@@ -1833,7 +1866,7 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error
|
||||
Host::SetBaseStringSettingValue("Achievements", "Username", params->username);
|
||||
Host::SetBaseStringSettingValue("Achievements", "LoginTimestamp", fmt::format("{}", std::time(nullptr)).c_str());
|
||||
Host::CommitBaseSettingChanges();
|
||||
|
||||
|
||||
SettingsInterface* secretsInterface = Host::Internal::GetSecretsSettingsLayer();
|
||||
secretsInterface->SetStringValue("Achievements", "Token", user->token);
|
||||
secretsInterface->Save();
|
||||
@@ -2032,7 +2065,7 @@ static ImVec2 CalculateOverlayPosition(const ImGuiIO& io, float padding, Achieve
|
||||
static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVec2& element_size, AchievementOverlayPosition alignment)
|
||||
{
|
||||
ImVec2 adjusted = base_position;
|
||||
|
||||
|
||||
// Adjust for horizontal alignment
|
||||
switch (alignment)
|
||||
{
|
||||
@@ -2041,14 +2074,14 @@ static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVe
|
||||
case AchievementOverlayPosition::BottomLeft:
|
||||
// Left aligned no adjustment needed for x
|
||||
break;
|
||||
|
||||
|
||||
case AchievementOverlayPosition::TopCenter:
|
||||
case AchievementOverlayPosition::Center:
|
||||
case AchievementOverlayPosition::BottomCenter:
|
||||
// Center aligned offset by half element width
|
||||
adjusted.x -= element_size.x * 0.5f;
|
||||
break;
|
||||
|
||||
|
||||
case AchievementOverlayPosition::TopRight:
|
||||
case AchievementOverlayPosition::CenterRight:
|
||||
case AchievementOverlayPosition::BottomRight:
|
||||
@@ -2057,7 +2090,7 @@ static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVe
|
||||
adjusted.x -= element_size.x;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// Adjust for vertical alignment
|
||||
switch (alignment)
|
||||
{
|
||||
@@ -2066,14 +2099,14 @@ static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVe
|
||||
case AchievementOverlayPosition::TopRight:
|
||||
// Top aligned no adjustment needed for y
|
||||
break;
|
||||
|
||||
|
||||
case AchievementOverlayPosition::CenterLeft:
|
||||
case AchievementOverlayPosition::Center:
|
||||
case AchievementOverlayPosition::CenterRight:
|
||||
// Center aligned offset by half element height
|
||||
adjusted.y -= element_size.y * 0.5f;
|
||||
break;
|
||||
|
||||
|
||||
case AchievementOverlayPosition::BottomLeft:
|
||||
case AchievementOverlayPosition::BottomCenter:
|
||||
case AchievementOverlayPosition::BottomRight:
|
||||
@@ -2082,7 +2115,7 @@ static ImVec2 AdjustPositionForAlignment(const ImVec2& base_position, const ImVe
|
||||
adjusted.y -= element_size.y;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
@@ -2094,19 +2127,19 @@ static ImVec2 GetStackingDirection(AchievementOverlayPosition alignment)
|
||||
case AchievementOverlayPosition::TopCenter:
|
||||
case AchievementOverlayPosition::TopRight:
|
||||
return ImVec2(0.0f, 1.0f); // Stack downward
|
||||
|
||||
|
||||
case AchievementOverlayPosition::BottomLeft:
|
||||
case AchievementOverlayPosition::BottomCenter:
|
||||
case AchievementOverlayPosition::BottomRight:
|
||||
default:
|
||||
return ImVec2(0.0f, -1.0f); // Stack upward
|
||||
|
||||
|
||||
case AchievementOverlayPosition::CenterLeft:
|
||||
return ImVec2(1.0f, 0.0f); // Stack rightward
|
||||
|
||||
|
||||
case AchievementOverlayPosition::CenterRight:
|
||||
return ImVec2(-1.0f, 0.0f); // Stack leftward
|
||||
|
||||
|
||||
case AchievementOverlayPosition::Center:
|
||||
return ImVec2(0.0f, -1.0f); // Stack upward for center
|
||||
}
|
||||
@@ -2147,7 +2180,7 @@ void Achievements::DrawGameOverlays()
|
||||
{
|
||||
dl->AddImage(reinterpret_cast<ImTextureID>(badge->GetNativeHandle()),
|
||||
current_position, current_position + image_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
|
||||
|
||||
|
||||
// For horizontal layouts, go horizontally for vertical layouts, stay in the same row
|
||||
if (std::abs(stack_direction.x) > 0.0f)
|
||||
{
|
||||
@@ -2198,7 +2231,7 @@ void Achievements::DrawGameOverlays()
|
||||
const ImVec2 progress_box_size = ImVec2(image_size.x + text_size.x + spacing + padding * 2.0f, image_size.y + padding * 2.0f);
|
||||
const ImVec2 stack_direction = GetStackingDirection(EmuConfig.Achievements.OverlayPosition);
|
||||
const ImVec2 box_position = AdjustPositionForAlignment(position, progress_box_size, EmuConfig.Achievements.OverlayPosition);
|
||||
|
||||
|
||||
const ImVec2 box_min = box_position;
|
||||
const ImVec2 box_max = box_position + progress_box_size;
|
||||
const float box_rounding = LayoutScale(1.0f);
|
||||
@@ -2232,7 +2265,7 @@ void Achievements::DrawGameOverlays()
|
||||
if (!s_active_leaderboard_trackers.empty() && EmuConfig.Achievements.LBOverlays)
|
||||
{
|
||||
const ImVec2 stack_direction = GetStackingDirection(EmuConfig.Achievements.OverlayPosition);
|
||||
|
||||
|
||||
for (auto it = s_active_leaderboard_trackers.begin(); it != s_active_leaderboard_trackers.end();)
|
||||
{
|
||||
const LeaderboardTrackerIndicator& indicator = *it;
|
||||
@@ -2247,7 +2280,7 @@ void Achievements::DrawGameOverlays()
|
||||
|
||||
const ImVec2 tracker_box_size = ImVec2(size.x + padding * 2.0f, size.y + padding * 2.0f);
|
||||
const ImVec2 box_position = AdjustPositionForAlignment(position, tracker_box_size, EmuConfig.Achievements.OverlayPosition);
|
||||
|
||||
|
||||
const ImVec2 box_min = box_position;
|
||||
const ImVec2 box_max = box_position + tracker_box_size;
|
||||
const float box_rounding = LayoutScale(1.0f);
|
||||
@@ -2468,7 +2501,8 @@ void Achievements::DrawAchievementsWindow()
|
||||
top += GetLineHeight(g_large_font) + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + GetLineHeight(g_medium_font)));
|
||||
@@ -2599,7 +2633,8 @@ void Achievements::DrawAchievementsWindow()
|
||||
const ImRect text_bb(ImVec2(text_x, bb.Min.y), ImVec2(bb.Max.x - padding, bb.Max.y));
|
||||
|
||||
ImGui::PushFont(g_medium_font.first, g_medium_font.second);
|
||||
ImGui::RenderTextClipped(text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
text_bb.Min, text_bb.Max, subset->title, nullptr, nullptr, ImVec2(0.0f, 0.5f), &text_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (ImGui::IsItemClicked() || ImGui::IsItemActivated())
|
||||
@@ -2688,21 +2723,22 @@ void Achievements::DrawAchievementsWindow()
|
||||
if (ImGuiFullscreen::IsGamepadInputSource())
|
||||
{
|
||||
const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons;
|
||||
const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs();
|
||||
if (s_sidebar_has_focus)
|
||||
{
|
||||
ImGuiFullscreen::SetFullscreenFooterText(std::array{
|
||||
std::make_pair(ICON_PF_DPAD_UP_DOWN, TRANSLATE_SV("Achievements", "Navigate Subsets")),
|
||||
std::make_pair(ICON_PF_DPAD_LEFT_RIGHT, TRANSLATE_SV("Achievements", "Back to List")),
|
||||
std::make_pair(circleOK ? ICON_PF_BUTTON_CIRCLE : ICON_PF_BUTTON_CROSS, TRANSLATE_SV("Achievements", "Select")),
|
||||
std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, TRANSLATE_SV("Achievements", "Back")),
|
||||
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Navigate Subsets")),
|
||||
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Back to List")),
|
||||
std::make_pair(glyphs.confirm(circleOK), TRANSLATE_SV("Achievements", "Select")),
|
||||
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiFullscreen::SetFullscreenFooterText(std::array{
|
||||
std::make_pair(ICON_PF_DPAD_LEFT_RIGHT, TRANSLATE_SV("Achievements", "Navigate Subsets")),
|
||||
std::make_pair(ICON_PF_DPAD_UP_DOWN, TRANSLATE_SV("Achievements", "Change Selection")),
|
||||
std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, TRANSLATE_SV("Achievements", "Back")),
|
||||
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Navigate Subsets")),
|
||||
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")),
|
||||
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2878,8 +2914,9 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
||||
ImVec2(points_template_start + ((points_template_size.x - right_icon_size.x) * 0.5f), bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, cheevo->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::RenderTextClipped(lock_bb.Min, lock_bb.Max, right_icon_text, nullptr, &right_icon_size, ImVec2(0.0f, 0.0f), &lock_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(title_bb.Min, title_bb.Max, cheevo->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
lock_bb.Min, lock_bb.Max, right_icon_text, nullptr, &right_icon_size, ImVec2(0.0f, 0.0f), &lock_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (badge_text && badge_icon)
|
||||
@@ -2905,14 +2942,16 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
||||
{
|
||||
ImGui::RenderTextWrapped(summary_bb.Min, cheevo->description, cheevo->description + summary_length, summary_wrap_width);
|
||||
}
|
||||
ImGui::RenderTextClipped(points_bb.Min, points_bb.Max, text.c_str(), text.end_ptr(), &points_size, ImVec2(0.0f, 0.0f), &points_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
points_bb.Min, points_bb.Max, text.c_str(), text.end_ptr(), &points_size, ImVec2(0.0f, 0.0f), &points_bb);
|
||||
|
||||
if (is_unlocked)
|
||||
{
|
||||
text.format(TRANSLATE_FS("Achievements", "Unlocked: {}"), FullscreenUI::TimeToPrintableString(cheevo->unlock_time));
|
||||
|
||||
const ImRect unlock_bb(summary_bb.Min.x, summary_bb.Max.y + spacing, summary_bb.Max.x, bb.Max.y);
|
||||
ImGui::RenderTextClipped(unlock_bb.Min, unlock_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &unlock_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
unlock_bb.Min, unlock_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &unlock_bb);
|
||||
}
|
||||
else if (is_measured)
|
||||
{
|
||||
@@ -3071,7 +3110,8 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
top += GetLineHeight(g_large_font) + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (is_leaderboard_open)
|
||||
@@ -3082,7 +3122,7 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
top += GetLineHeight(g_large_font) + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::RenderTextClipped(
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
subtitle_bb.Min, subtitle_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &subtitle_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
@@ -3100,7 +3140,7 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
top += GetLineHeight(g_medium_font) + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_medium_font.first, g_medium_font.second);
|
||||
ImGui::RenderTextClipped(
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
summary_bb.Min, summary_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
|
||||
if (!is_leaderboard_open && !Achievements::IsHardcoreModeActive())
|
||||
@@ -3108,9 +3148,9 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + GetLineHeight(g_medium_font)));
|
||||
top += GetLineHeight(g_medium_font) + spacing_small;
|
||||
|
||||
ImGui::RenderTextClipped(hardcore_warning_bb.Min, hardcore_warning_bb.Max,
|
||||
TRANSLATE("Achievements", "Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only."),
|
||||
nullptr, nullptr, ImVec2(0.0f, 0.0f), &hardcore_warning_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(hardcore_warning_bb.Min, hardcore_warning_bb.Max,
|
||||
TRANSLATE("Achievements", "Submitting scores is disabled because hardcore mode is off. Leaderboards are read-only."), nullptr,
|
||||
nullptr, ImVec2(0.0f, 0.0f), &hardcore_warning_bb);
|
||||
}
|
||||
|
||||
ImGui::PopFont();
|
||||
@@ -3159,7 +3199,7 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
|
||||
const auto render_centered_heading = [&](const char* text, float width) {
|
||||
const ImRect rect(ImVec2(column_left, bb.Min.y), ImVec2(column_left + width, midpoint));
|
||||
ImGui::RenderTextClipped(rect.Min, rect.Max, text, nullptr, nullptr, ImVec2(0.5f, 0.0f), &rect);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(rect.Min, rect.Max, text, nullptr, nullptr, ImVec2(0.5f, 0.0f), &rect);
|
||||
column_left += width + column_spacing;
|
||||
};
|
||||
|
||||
@@ -3168,7 +3208,7 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
const float name_column_total_width = icon_column_width + column_spacing + name_column_width;
|
||||
const ImRect user_bb(ImVec2(column_left + icon_column_width + column_spacing, bb.Min.y),
|
||||
ImVec2(column_left + name_column_total_width, midpoint));
|
||||
ImGui::RenderTextClipped(
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
user_bb.Min, user_bb.Max, TRANSLATE("Achievements", "Name"), nullptr, nullptr, ImVec2(0.5f, 0.0f), &user_bb);
|
||||
column_left += name_column_total_width + column_spacing;
|
||||
|
||||
@@ -3185,8 +3225,8 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
|
||||
const float date_column_width = std::max(bb.Max.x - column_left, 0.0f);
|
||||
const ImRect date_bb(ImVec2(column_left, bb.Min.y), ImVec2(column_left + date_column_width, midpoint));
|
||||
ImGui::RenderTextClipped(date_bb.Min, date_bb.Max, TRANSLATE("Achievements", "Date Submitted"), nullptr, nullptr,
|
||||
ImVec2(0.5f, 0.0f), &date_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
date_bb.Min, date_bb.Max, TRANSLATE("Achievements", "Date Submitted"), nullptr, nullptr, ImVec2(0.5f, 0.0f), &date_bb);
|
||||
column_left += date_column_width;
|
||||
|
||||
ImGui::PopFont();
|
||||
@@ -3303,10 +3343,11 @@ void Achievements::DrawLeaderboardsWindow()
|
||||
if (ImGuiFullscreen::IsGamepadInputSource())
|
||||
{
|
||||
const bool circleOK = ImGui::GetIO().ConfigNavSwapGamepadButtons;
|
||||
const auto glyphs = ImGuiFullscreen::GetGamepadGlyphs();
|
||||
ImGuiFullscreen::SetFullscreenFooterText(std::array{
|
||||
std::make_pair(ICON_PF_DPAD_LEFT_RIGHT, TRANSLATE_SV("Achievements", "Switch Rankings")),
|
||||
std::make_pair(ICON_PF_DPAD_UP_DOWN, TRANSLATE_SV("Achievements", "Change Selection")),
|
||||
std::make_pair(circleOK ? ICON_PF_BUTTON_CROSS : ICON_PF_BUTTON_CIRCLE, TRANSLATE_SV("Achievements", "Back")),
|
||||
std::make_pair(glyphs.dpad_lr, TRANSLATE_SV("Achievements", "Switch Rankings")),
|
||||
std::make_pair(glyphs.dpad_ud, TRANSLATE_SV("Achievements", "Change Selection")),
|
||||
std::make_pair(glyphs.cancel(circleOK), TRANSLATE_SV("Achievements", "Back")),
|
||||
});
|
||||
}
|
||||
else
|
||||
@@ -3353,7 +3394,8 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 242, 0, 255));
|
||||
|
||||
const ImRect rank_bb(ImVec2(column_left, bb.Min.y), ImVec2(column_left + rank_column_width, midpoint));
|
||||
ImGui::RenderTextClipped(rank_bb.Min, rank_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &rank_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
rank_bb.Min, rank_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &rank_bb);
|
||||
column_left += rank_column_width + column_spacing;
|
||||
|
||||
const ImRect icon_bb(ImVec2(column_left, bb.Min.y), ImVec2(column_left + icon_column_width, bb.Min.y + icon_column_width));
|
||||
@@ -3383,16 +3425,18 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
||||
column_left += icon_column_width + column_spacing;
|
||||
|
||||
const ImRect user_bb(ImVec2(column_left, bb.Min.y), ImVec2(column_left + name_column_width, midpoint));
|
||||
ImGui::RenderTextClipped(user_bb.Min, user_bb.Max, entry.user, nullptr, nullptr, ImVec2(0.0f, 0.0f), &user_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(user_bb.Min, user_bb.Max, entry.user, nullptr, nullptr, ImVec2(0.0f, 0.0f), &user_bb);
|
||||
column_left += name_column_width + column_spacing;
|
||||
|
||||
const ImRect score_bb(ImVec2(column_left, bb.Min.y), ImVec2(column_left + time_column_width, midpoint));
|
||||
ImGui::RenderTextClipped(score_bb.Min, score_bb.Max, entry.display, nullptr, nullptr, ImVec2(0.0f, 0.0f), &score_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
score_bb.Min, score_bb.Max, entry.display, nullptr, nullptr, ImVec2(0.0f, 0.0f), &score_bb);
|
||||
column_left += time_column_width + column_spacing;
|
||||
|
||||
const ImRect time_bb(ImVec2(column_left, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
const auto submit_time = FullscreenUI::TimeToPrintableString(entry.submitted);
|
||||
ImGui::RenderTextClipped(time_bb.Min, time_bb.Max, submit_time.c_str(), submit_time.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &time_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
time_bb.Min, time_bb.Max, submit_time.c_str(), submit_time.end_ptr(), nullptr, ImVec2(0.0f, 0.0f), &time_bb);
|
||||
|
||||
if (is_self)
|
||||
ImGui::PopStyleColor();
|
||||
@@ -3436,13 +3480,14 @@ void Achievements::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboar
|
||||
const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max);
|
||||
|
||||
ImGui::PushFont(g_large_font.first, g_large_font.second);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, lboard->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(title_bb.Min, title_bb.Max, lboard->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (lboard->description && lboard->description[0] != '\0')
|
||||
{
|
||||
ImGui::PushFont(g_medium_font.first, g_medium_font.second);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, lboard->description, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
ImGuiFullscreen::RenderTextClippedWithShadow(
|
||||
summary_bb.Min, summary_bb.Max, lboard->description, nullptr, nullptr, ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
@@ -3693,7 +3738,13 @@ void Achievements::RAIntegration::RACallbackCausePause()
|
||||
|
||||
void Achievements::RAIntegration::RACallbackRebuildMenu()
|
||||
{
|
||||
// unused, we build the menu on demand
|
||||
// Sync hardcore state in case the user changed it via the RA menu.
|
||||
const bool ra_hardcore = (RA_HardcoreModeIsActive() != 0);
|
||||
if (ra_hardcore != s_hardcore_mode)
|
||||
{
|
||||
s_hardcore_mode = ra_hardcore;
|
||||
Host::OnAchievementsHardcoreModeChanged(ra_hardcore);
|
||||
}
|
||||
}
|
||||
|
||||
void Achievements::RAIntegration::RACallbackEstimateTitle(char* buf)
|
||||
|
||||
+3
-3
@@ -792,6 +792,7 @@ struct Pcsx2Config
|
||||
Mipmap : 1,
|
||||
HWMipmap : 1,
|
||||
HWAccurateAlphaTest: 1,
|
||||
HWAA1 : 1,
|
||||
ManualUserHacks : 1,
|
||||
UserHacks_AlignSpriteX : 1,
|
||||
UserHacks_CPUFBConversion : 1,
|
||||
@@ -804,6 +805,7 @@ struct Pcsx2Config
|
||||
UserHacks_ForceEvenSpritePosition : 1,
|
||||
UserHacks_NativePaletteDraw : 1,
|
||||
UserHacks_EstimateTextureRegion : 1,
|
||||
UserHacks_DrawBuffering : 1,
|
||||
FXAA : 1,
|
||||
ShadeBoost : 1,
|
||||
DumpGSData : 1,
|
||||
@@ -1285,9 +1287,6 @@ struct Pcsx2Config
|
||||
static constexpr u32 MAXIMUM_NOTIFICATION_DURATION = 30;
|
||||
static constexpr u32 DEFAULT_NOTIFICATION_DURATION = 5;
|
||||
static constexpr u32 DEFAULT_LEADERBOARD_DURATION = 10;
|
||||
static constexpr const char* DEFAULT_INFO_SOUND_NAME = "sounds/achievements/message.wav";
|
||||
static constexpr const char* DEFAULT_UNLOCK_SOUND_NAME = "sounds/achievements/unlock.wav";
|
||||
static constexpr const char* DEFAULT_LBSUBMIT_SOUND_NAME = "sounds/achievements/lbsubmit.wav";
|
||||
|
||||
static const char* OverlayPositionNames[(size_t)AchievementOverlayPosition::MaxCount + 1];
|
||||
|
||||
@@ -1404,6 +1403,7 @@ struct Pcsx2Config
|
||||
std::string CurrentBlockdump;
|
||||
std::string CurrentIRX;
|
||||
std::string CurrentGameArgs;
|
||||
std::string CustomDataPath;
|
||||
AspectRatioType CurrentAspectRatio = AspectRatioType::RAuto4_3_3_2;
|
||||
// Fall back aspect ratio for games that have patches (when AspectRatioType::RAuto4_3_3_2) is active.
|
||||
float CurrentCustomAspectRatio = 0.f;
|
||||
|
||||
+1
-1
@@ -876,7 +876,7 @@ static __fi void rcntEndGate(bool isVblank, u64 sCycle)
|
||||
isVblank ? "vblank" : "hblank", counters[i].mode.GateMode, counters[i].count);
|
||||
counters[i].count = 0;
|
||||
counters[i].target &= 0xffff;
|
||||
counters[i].startCycle = sCycle & ~(counters[i].rate - 1);
|
||||
counters[i].startCycle = sCycle & ~(static_cast<u64>(counters[i].rate - 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
|
||||
return {};
|
||||
}
|
||||
|
||||
data.stackTop = iopMemRead32(item + 0x3c);
|
||||
data.stacMem = iopMemRead32(item + 0x3c);
|
||||
data.stackSize = iopMemRead32(item + 0x40);
|
||||
data.status = iopMemRead8(item + 0xc);
|
||||
data.tid = iopMemRead16(item + 0xa);
|
||||
data.entrypoint = iopMemRead32(item + 0x38);
|
||||
@@ -57,9 +58,9 @@ std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
|
||||
data.waitId = iopMemRead32(item + 0x20);
|
||||
data.initPriority = iopMemRead16(item + 0xe);
|
||||
|
||||
data.SavedSP = iopMemRead32(item + 0x10);
|
||||
data.regCtx = iopMemRead32(item + 0x10);
|
||||
|
||||
data.PC = iopMemRead32(data.SavedSP + 0x8c);
|
||||
data.PC = iopMemRead32(data.regCtx + 0x8c);
|
||||
|
||||
threads.emplace_back(std::make_unique<IOPThread>(data));
|
||||
|
||||
|
||||
@@ -18,26 +18,37 @@ enum class ThreadStatus
|
||||
THS_DORMANT = 0x10,
|
||||
};
|
||||
|
||||
struct EEInternalCtx
|
||||
{
|
||||
u32 sa;
|
||||
u32 fcsr;
|
||||
u32 float_thing;
|
||||
u32 unk;
|
||||
// gpr excluding $zero
|
||||
// k0/k1 contains hi, hi1, lo, lo1
|
||||
u128 gpr[31];
|
||||
float fpr[32];
|
||||
};
|
||||
|
||||
struct EEInternalThread
|
||||
{ // internal struct
|
||||
u32 prev;
|
||||
u32 next;
|
||||
int status;
|
||||
u32 entry;
|
||||
u32 stack;
|
||||
u32 resumeAddr; // address to return to when switching
|
||||
u32 regCtx; // points to the saved regs on stack
|
||||
u32 gpReg;
|
||||
short currentPriority;
|
||||
short initPriority;
|
||||
short currentPriority;
|
||||
int waitType;
|
||||
int semaId;
|
||||
int wakeupCount;
|
||||
int attr;
|
||||
int option;
|
||||
u32 entry_init;
|
||||
u32 entry;
|
||||
int argc;
|
||||
u32 argstring;
|
||||
u32 stack_bottom; //FIXME
|
||||
u32 stackMem;
|
||||
int stackSize;
|
||||
u32 root;
|
||||
u32 heap_base;
|
||||
@@ -48,8 +59,9 @@ struct IOPInternalThread
|
||||
{
|
||||
u32 tid;
|
||||
u32 PC;
|
||||
u32 stackTop;
|
||||
u32 SavedSP;
|
||||
u32 stacMem;
|
||||
u32 stackSize;
|
||||
u32 regCtx;
|
||||
u32 status;
|
||||
u32 entrypoint;
|
||||
u32 waitstate;
|
||||
@@ -71,14 +83,13 @@ enum class IOPWaitStatus
|
||||
enum class EEWaitStatus
|
||||
{
|
||||
WAIT_NONE = 0,
|
||||
WAIT_WAKEUP_REQ = 1,
|
||||
WAIT_SLEEP = 1,
|
||||
WAIT_SEMA = 2,
|
||||
};
|
||||
|
||||
enum class WaitState
|
||||
{
|
||||
NONE,
|
||||
WAKEUP_REQ,
|
||||
SEMA,
|
||||
SLEEP,
|
||||
DELAY,
|
||||
@@ -98,8 +109,10 @@ public:
|
||||
[[nodiscard]] virtual WaitState Wait() const = 0;
|
||||
[[nodiscard]] virtual u32 WaitId() const = 0;
|
||||
[[nodiscard]] virtual u32 EntryPoint() const = 0;
|
||||
[[nodiscard]] virtual u32 StackTop() const = 0;
|
||||
[[nodiscard]] virtual u32 Priority() const = 0;
|
||||
|
||||
// Only call RegCtx on threads that aren't running
|
||||
[[nodiscard]] virtual u32 RegCtx() const = 0;
|
||||
};
|
||||
|
||||
class EEThread : public BiosThread
|
||||
@@ -113,7 +126,7 @@ public:
|
||||
~EEThread() override = default;
|
||||
|
||||
[[nodiscard]] u32 TID() const override { return tid; };
|
||||
[[nodiscard]] u32 PC() const override { return data.entry; };
|
||||
[[nodiscard]] u32 PC() const override { return data.resumeAddr; };
|
||||
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
|
||||
[[nodiscard]] WaitState Wait() const override
|
||||
{
|
||||
@@ -122,17 +135,17 @@ public:
|
||||
{
|
||||
case EEWaitStatus::WAIT_NONE:
|
||||
return WaitState::NONE;
|
||||
case EEWaitStatus::WAIT_WAKEUP_REQ:
|
||||
return WaitState::WAKEUP_REQ;
|
||||
case EEWaitStatus::WAIT_SLEEP:
|
||||
return WaitState::SLEEP;
|
||||
case EEWaitStatus::WAIT_SEMA:
|
||||
return WaitState::SEMA;
|
||||
}
|
||||
return WaitState::NONE;
|
||||
};
|
||||
[[nodiscard]] u32 WaitId() const override { return data.semaId; };
|
||||
[[nodiscard]] u32 EntryPoint() const override { return data.entry_init; };
|
||||
[[nodiscard]] u32 StackTop() const override { return data.stack; };
|
||||
[[nodiscard]] u32 EntryPoint() const override { return data.entry; };
|
||||
[[nodiscard]] u32 Priority() const override { return data.currentPriority; };
|
||||
[[nodiscard]] u32 RegCtx() const override { return data.regCtx; };
|
||||
|
||||
private:
|
||||
u32 tid;
|
||||
@@ -175,8 +188,8 @@ public:
|
||||
};
|
||||
[[nodiscard]] u32 WaitId() const override { return data.waitId; };
|
||||
[[nodiscard]] u32 EntryPoint() const override { return data.entrypoint; };
|
||||
[[nodiscard]] u32 StackTop() const override { return data.stackTop; };
|
||||
[[nodiscard]] u32 Priority() const override { return data.initPriority; };
|
||||
[[nodiscard]] u32 RegCtx() const override { return data.regCtx; };
|
||||
|
||||
private:
|
||||
IOPInternalThread data;
|
||||
|
||||
@@ -708,6 +708,23 @@ std::vector<std::unique_ptr<BiosThread>> R5900DebugInterface::GetThreadList() co
|
||||
return getEEThreads();
|
||||
}
|
||||
|
||||
std::vector<MipsStackWalk::StackFrame> R5900DebugInterface::StackTrace(const BiosThread& thread)
|
||||
{
|
||||
if (thread.Status() == ThreadStatus::THS_RUN)
|
||||
{
|
||||
return MipsStackWalk::Walk(this, getPC(), getRegister(0, 31), getRegister(0, 29),
|
||||
thread.EntryPoint());
|
||||
}
|
||||
|
||||
EEInternalCtx* ctx = static_cast<EEInternalCtx*>(PSM(thread.RegCtx()));
|
||||
u32 pc = thread.PC();
|
||||
// $zero is not in the array so subtract 1
|
||||
u32 ra = ctx->gpr[31 - 1]._u32[0];
|
||||
u32 sp = ctx->gpr[29 - 1]._u32[0];
|
||||
|
||||
return MipsStackWalk::Walk(this, pc, ra, sp, thread.EntryPoint());
|
||||
}
|
||||
|
||||
std::vector<IopMod> R5900DebugInterface::GetModuleList() const
|
||||
{
|
||||
return {};
|
||||
@@ -1049,6 +1066,22 @@ std::vector<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() co
|
||||
return getIOPThreads();
|
||||
}
|
||||
|
||||
std::vector<MipsStackWalk::StackFrame> R3000DebugInterface::StackTrace(const BiosThread& thread)
|
||||
{
|
||||
if (thread.Status() == ThreadStatus::THS_RUN)
|
||||
{
|
||||
return MipsStackWalk::Walk(this, getPC(), getRegister(0, 31), getRegister(0, 29),
|
||||
thread.EntryPoint());
|
||||
}
|
||||
|
||||
u32 p = thread.RegCtx();
|
||||
u32 pc = Read32(p + 0x8c);
|
||||
u32 ra = Read32(p + 0x7c);
|
||||
u32 sp = Read32(p + 0x74);
|
||||
|
||||
return MipsStackWalk::Walk(this, pc, ra, sp, thread.EntryPoint());
|
||||
}
|
||||
|
||||
std::vector<IopMod> R3000DebugInterface::GetModuleList() const
|
||||
{
|
||||
return getIOPModules();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "ExpressionParser.h"
|
||||
#include "SymbolGuardian.h"
|
||||
#include "SymbolImporter.h"
|
||||
#include "MipsStackWalk.h"
|
||||
|
||||
#include "common/MemoryInterface.h"
|
||||
|
||||
@@ -72,6 +73,7 @@ public:
|
||||
virtual SymbolGuardian& GetSymbolGuardian() const = 0;
|
||||
virtual SymbolImporter* GetSymbolImporter() const = 0;
|
||||
virtual std::vector<std::unique_ptr<BiosThread>> GetThreadList() const = 0;
|
||||
virtual std::vector<MipsStackWalk::StackFrame> StackTrace(const BiosThread& thread) = 0;
|
||||
virtual std::vector<IopMod> GetModuleList() const = 0;
|
||||
|
||||
bool isAlive();
|
||||
@@ -135,6 +137,7 @@ public:
|
||||
SymbolGuardian& GetSymbolGuardian() const override;
|
||||
SymbolImporter* GetSymbolImporter() const override;
|
||||
std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
|
||||
std::vector<MipsStackWalk::StackFrame> StackTrace(const BiosThread& thread) override;
|
||||
std::vector<IopMod> GetModuleList() const override;
|
||||
|
||||
std::string disasm(u32 address, bool simplify) override;
|
||||
@@ -180,6 +183,7 @@ public:
|
||||
SymbolGuardian& GetSymbolGuardian() const override;
|
||||
SymbolImporter* GetSymbolImporter() const override;
|
||||
std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
|
||||
std::vector<MipsStackWalk::StackFrame> StackTrace(const BiosThread& thread) override;
|
||||
std::vector<IopMod> GetModuleList() const override;
|
||||
|
||||
std::string disasm(u32 address, bool simplify) override;
|
||||
|
||||
@@ -103,7 +103,10 @@ namespace MipsStackWalk
|
||||
// TODO: Check if found entry is in the same symbol? Might be wrong sometimes...
|
||||
|
||||
int ra_offset = -1;
|
||||
const u32 start = frame.pc;
|
||||
|
||||
// The instruction pointed to by pc hasn't been executed yet,
|
||||
// so we don't want to consider it here
|
||||
const u32 start = frame.pc - 4;
|
||||
u32 stop = entry;
|
||||
|
||||
if (entry == INVALIDTARGET)
|
||||
@@ -183,7 +186,7 @@ namespace MipsStackWalk
|
||||
return ScanForEntry(cpu, frame, newPossibleEntry, ra);
|
||||
}
|
||||
|
||||
std::vector<StackFrame> Walk(DebugInterface* cpu, u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop)
|
||||
std::vector<StackFrame> Walk(DebugInterface* cpu, u32 pc, u32 ra, u32 sp, u32 threadEntry)
|
||||
{
|
||||
std::vector<StackFrame> frames;
|
||||
StackFrame current;
|
||||
|
||||
@@ -20,5 +20,5 @@ namespace MipsStackWalk {
|
||||
int stackSize;
|
||||
};
|
||||
|
||||
std::vector<StackFrame> Walk(DebugInterface* cpu, u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop);
|
||||
std::vector<StackFrame> Walk(DebugInterface* cpu, u32 pc, u32 ra, u32 sp, u32 threadEntry);
|
||||
};
|
||||
|
||||
@@ -174,6 +174,7 @@ The clamp modes are also numerically based.
|
||||
* cpuSpriteRenderBW [Value between `0` to `10`] {Disabled, 1 (64), 2 (128), 3 (192), 4 (256), 5 (320), 6 (384), 7 (448), 8 (512), 9 (576), 10 (640)} Default: Off (`0`)
|
||||
* cpuSpriteRenderLevel [`0` or `1` or `2`] {Sprites only, Sprites/Triangles, Blended Sprites/Triangles} Default: Off unless cpuSpriteRenderBW has value other than Off then it is 'Sprites only' (`0`)
|
||||
* estimateTextureRegion [`0` or `1`] {Off, On} Default: Off (`0`)
|
||||
* drawBuffering [`0` or `1`] {Off, On} Default: Off (`0`)
|
||||
* getSkipCount {`GSC` with suffix } {None unless specific game GSC} Default: Disabled (`0`) unless valid variable name (ex. GSC_PolyphonyDigitalGames, GSC_UrbanReign, ...)
|
||||
* gpuPaletteConversion [`0` or `1`] {Off, On} Default: Off (`0`)
|
||||
* gpuTargetCLUT [`0` or `1` or `2`] {Disabled, Enabled (Exact Match), Enabled (Check Inside Target)} Default: Disabled (`0`)
|
||||
|
||||
@@ -203,6 +203,11 @@
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"drawBuffering": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1
|
||||
},
|
||||
"PCRTCOffsets": {
|
||||
"type": "integer",
|
||||
|
||||
@@ -91,11 +91,11 @@ void GSDrawingContext::UpdateScissor()
|
||||
scissor.in = rscissor + GSVector4i::cxpr(0, 0, 1, 1);
|
||||
|
||||
// Fixed-point scissor min/max, used for rejecting primitives which are entirely outside.
|
||||
scissor.cull = rscissor.sll32<4>();
|
||||
// Add half a pixel around the edges for upscaling and lines/points.
|
||||
scissor.cull = rscissor.sll32<4>() + GSVector4i(-8, -8, 8, 8);
|
||||
|
||||
// Offset applied to vertices for culling, zw is for native resolution culling
|
||||
// We want to round subpixels down, because at least one pixel gets filled per scanline.
|
||||
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy().sub32(GSVector4i::cxpr(0, 0, 15, 15));
|
||||
// Offset applied to vertices for culling.
|
||||
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy();
|
||||
}
|
||||
|
||||
GIFRegTEX0 GSDrawingContext::GetSizeFixedTEX0(const GSVector4& st, bool linear, bool mipmap) const
|
||||
|
||||
+14
-45
@@ -512,56 +512,25 @@ GSVector4i GSLocalMemory::GetRectForPageOffset(u32 base_bp, u32 offset_bp, u32 b
|
||||
bool GSLocalMemory::HasOverlap(const u32 src_bp, const u32 src_bw, const u32 src_psm, const GSVector4i src_rect,
|
||||
const u32 dst_bp, const u32 dst_bw, const u32 dst_psm, const GSVector4i dst_rect)
|
||||
{
|
||||
const u32 src_start_bp = GSLocalMemory::GetStartBlockAddress(src_bp, src_bw, src_psm, src_rect) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
const u32 dst_start_bp = GSLocalMemory::GetStartBlockAddress(dst_bp, dst_bw, dst_psm, dst_rect) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
u32 src_start_bp = GSLocalMemory::GetStartBlockAddress(src_bp, src_bw, src_psm, src_rect) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
u32 dst_start_bp = GSLocalMemory::GetStartBlockAddress(dst_bp, dst_bw, dst_psm, dst_rect) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
|
||||
u32 src_end_bp = ((GSLocalMemory::GetEndBlockAddress(src_bp, src_bw, src_psm, src_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
u32 dst_end_bp = ((GSLocalMemory::GetEndBlockAddress(dst_bp, dst_bw, dst_psm, dst_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
u32 src_end_bp = ((GSLocalMemory::GetUnwrappedEndBlockAddress(src_bp, src_bw, src_psm, src_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
u32 dst_end_bp = ((GSLocalMemory::GetUnwrappedEndBlockAddress(dst_bp, dst_bw, dst_psm, dst_rect) + 1) + (GS_BLOCKS_PER_PAGE - 1)) & ~(GS_BLOCKS_PER_PAGE - 1);
|
||||
|
||||
if (src_start_bp == src_end_bp)
|
||||
if (dst_end_bp > GS_MAX_BLOCKS && src_end_bp < dst_start_bp)
|
||||
{
|
||||
src_end_bp = (src_end_bp + GS_BLOCKS_PER_PAGE) & ~(GS_MAX_BLOCKS - 1);
|
||||
src_start_bp += GS_MAX_BLOCKS;
|
||||
src_end_bp += GS_MAX_BLOCKS;
|
||||
}
|
||||
else if (src_end_bp > GS_MAX_BLOCKS && dst_end_bp < src_start_bp)
|
||||
{
|
||||
dst_start_bp += GS_MAX_BLOCKS;
|
||||
dst_end_bp += GS_MAX_BLOCKS;
|
||||
}
|
||||
|
||||
if (dst_start_bp == dst_end_bp)
|
||||
{
|
||||
dst_end_bp = (dst_end_bp + GS_BLOCKS_PER_PAGE) & ~(GS_MAX_BLOCKS - 1);
|
||||
}
|
||||
|
||||
// Source has wrapped, 2 separate checks.
|
||||
if (src_end_bp <= src_start_bp)
|
||||
{
|
||||
// Destination has also wrapped, so they *have* to overlap.
|
||||
if (dst_end_bp <= dst_start_bp)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dst_end_bp > src_start_bp)
|
||||
return true;
|
||||
|
||||
if (dst_start_bp < src_end_bp)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // No wrapping on source.
|
||||
{
|
||||
// Destination wraps.
|
||||
if (dst_end_bp <= dst_start_bp)
|
||||
{
|
||||
if (src_end_bp > dst_start_bp)
|
||||
return true;
|
||||
|
||||
if (src_start_bp < dst_end_bp)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dst_start_bp < src_end_bp && dst_end_bp > src_start_bp)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (src_start_bp < dst_end_bp && dst_start_bp < src_end_bp)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
+1134
-493
File diff suppressed because it is too large
Load Diff
+45
-20
@@ -127,37 +127,39 @@ private:
|
||||
|
||||
protected:
|
||||
static constexpr int INVALID_ALPHA_MINMAX = 500;
|
||||
static constexpr int MAX_DRAW_BUFFERS = 3;
|
||||
|
||||
GSVertex m_v = {};
|
||||
float m_q = 1.0f;
|
||||
GSVector4i m_scissor_cull_min = {};
|
||||
GSVector4i m_scissor_cull_max = {};
|
||||
GSVector4i m_xyof = {};
|
||||
int m_used_buffers_idx = 0;
|
||||
int m_current_buffer_idx = 0;
|
||||
bool m_recent_buffer_switch = false;
|
||||
|
||||
struct
|
||||
struct GSVertexBuff
|
||||
{
|
||||
GSVertex* buff;
|
||||
GSVertex* buff_copy; // same size buffer to copy/modify the original buffer
|
||||
GSVertex* buff_copy; // same size buffer to copy/modify the original buffer
|
||||
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
|
||||
u32 xy_tail;
|
||||
GSVector4i xy[4];
|
||||
GSVector4i xyhead;
|
||||
} m_vertex = {};
|
||||
};
|
||||
|
||||
struct
|
||||
GSVertexBuff m_vertex_buffers[MAX_DRAW_BUFFERS];
|
||||
GSVertexBuff* m_vertex;
|
||||
|
||||
struct GSIndexBuff
|
||||
{
|
||||
u16* buff;
|
||||
u32 tail;
|
||||
} m_index = {};
|
||||
};
|
||||
|
||||
struct
|
||||
{
|
||||
GSVertex* buff;
|
||||
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
|
||||
u32 xy_tail;
|
||||
GSVector4i xy[4];
|
||||
GSVector4i xyhead;
|
||||
} m_draw_vertex = {};
|
||||
GSIndexBuff m_index_buffers[MAX_DRAW_BUFFERS];
|
||||
|
||||
GSIndexBuff* m_index;
|
||||
|
||||
GSVertexBuff m_draw_vertex = {};
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -165,6 +167,17 @@ protected:
|
||||
u32 tail;
|
||||
} m_draw_index = {};
|
||||
|
||||
struct GSDrawBufferEnv
|
||||
{
|
||||
GSDrawingEnvironment m_env;
|
||||
int m_backed_up_ctx = 0;
|
||||
u32 m_dirty_regs = 0;
|
||||
GSVector4i draw_rect = GSVector4i::zero();
|
||||
bool related_draw = false;
|
||||
};
|
||||
|
||||
GSDrawBufferEnv m_env_buffers[MAX_DRAW_BUFFERS] = {};
|
||||
|
||||
void UpdateContext();
|
||||
void UpdateScissor();
|
||||
|
||||
@@ -175,6 +188,7 @@ protected:
|
||||
template<u32 prim> void HandleAutoFlush();
|
||||
bool EarlyDetectShuffle(u32 prim);
|
||||
void CheckCLUTValidity(u32 prim);
|
||||
bool CheckOverlapVerts(u32 n);
|
||||
|
||||
template <u32 prim, bool auto_flush> void VertexKick(u32 skip);
|
||||
|
||||
@@ -208,6 +222,8 @@ protected:
|
||||
bool IsMipMapDraw();
|
||||
bool IsMipMapActive();
|
||||
bool IsCoverageAlpha();
|
||||
bool IsCoverageAlphaFixedOne();
|
||||
virtual bool IsCoverageAlphaSupported();
|
||||
void CalcAlphaMinMax(const int tex_min, const int tex_max);
|
||||
void CorrectATEAlphaMinMax(const u32 atst, const int aref);
|
||||
|
||||
@@ -242,15 +258,15 @@ public:
|
||||
{
|
||||
EE_to_GS,
|
||||
GS_to_GS,
|
||||
GS_to_EE
|
||||
GS_to_EE,
|
||||
Clear
|
||||
};
|
||||
|
||||
struct GSUploadQueue
|
||||
{
|
||||
GIFRegBITBLTBUF blit;
|
||||
GSVector4i rect;
|
||||
u64 draw;
|
||||
bool zero_clear;
|
||||
GSVector4i rect;
|
||||
EEGS_TransferType transfer_type;
|
||||
};
|
||||
|
||||
@@ -268,9 +284,10 @@ public:
|
||||
GSLocalMemory m_mem;
|
||||
GSDrawingEnvironment m_env = {};
|
||||
GSDrawingEnvironment m_prev_env = {};
|
||||
GSDrawingEnvironment m_temp_env = {};
|
||||
const GSDrawingEnvironment* m_draw_env = &m_env;
|
||||
GSDrawingContext* m_context = nullptr;
|
||||
GSVector4i temp_draw_rect = {};
|
||||
GSVector4i temp_draw_rect;
|
||||
std::unique_ptr<GSDumpBase> m_dump;
|
||||
bool m_scissor_invalid = false;
|
||||
bool m_quad_check_valid = false;
|
||||
@@ -468,7 +485,15 @@ public:
|
||||
virtual void Reset(bool hardware_reset);
|
||||
virtual void UpdateSettings(const Pcsx2Config::GSOptions& old_config);
|
||||
|
||||
void ResetDrawBuffers();
|
||||
void ResetDrawBufferIdx();
|
||||
void FlushBuffers(bool flush_base_only = false, bool use_flush_reason = false, GSFlushReason flush_reason = GSFlushReason::CONTEXTCHANGE);
|
||||
void PushBuffer();
|
||||
void SetDrawBufferEnv();
|
||||
void SetDrawBuffDirty();
|
||||
bool CanBufferNewDraw();
|
||||
void Flush(GSFlushReason reason);
|
||||
void FlushDraw(GSFlushReason reason);
|
||||
u32 CalcMask(int exp, int max_exp);
|
||||
void FlushPrim();
|
||||
bool TestDrawChanged();
|
||||
@@ -481,7 +506,7 @@ public:
|
||||
|
||||
virtual void Move();
|
||||
|
||||
GSVector4i GetTEX0Rect();
|
||||
GSVector4i GetTEX0Rect(GSDrawingContext prev_ctx);
|
||||
void CheckWriteOverlap(bool req_write, bool req_read);
|
||||
void Write(const u8* mem, int len);
|
||||
void Read(u8* mem, int len);
|
||||
|
||||
@@ -339,3 +339,27 @@ const char* GSUtil::GetPSMName(int psm)
|
||||
}
|
||||
return "BAD_PSM";
|
||||
}
|
||||
|
||||
bool GSUtil::IsValidPSM(int psm)
|
||||
{
|
||||
switch (psm)
|
||||
{
|
||||
case PSMCT32:
|
||||
case PSMCT24:
|
||||
case PSMCT16:
|
||||
case PSMCT16S:
|
||||
case PSMT8:
|
||||
case PSMT4:
|
||||
case PSMT8H:
|
||||
case PSMT4HL:
|
||||
case PSMT4HH:
|
||||
case PSMZ32:
|
||||
case PSMZ24:
|
||||
case PSMZ16:
|
||||
case PSMZ16S:
|
||||
case PSGPU24:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "GS.h"
|
||||
#include "GSRegs.h"
|
||||
#include "GSPerfMon.h"
|
||||
#include <climits>
|
||||
|
||||
class GSUtil
|
||||
{
|
||||
@@ -28,6 +29,8 @@ public:
|
||||
static const char* GetACName(u32 ac);
|
||||
static const char* GetPerfMonCounterName(GSPerfMon::counter_t counter, bool hw = true);
|
||||
|
||||
static bool IsValidPSM(int psm);
|
||||
|
||||
static const u32* HasSharedBitsPtr(u32 dpsm);
|
||||
static bool HasSharedBits(u32 spsm, const u32* ptr);
|
||||
static bool HasSharedBits(u32 spsm, u32 dpsm);
|
||||
@@ -157,6 +160,14 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
BoundingOct ExpandOne() const
|
||||
{
|
||||
return {
|
||||
bbox0 + GSVector4i(-1, -1, 1, 1),
|
||||
bbox1 + GSVector4i(-1, -1, 1, 1),
|
||||
};
|
||||
}
|
||||
|
||||
const GSVector4i& ToBBox()
|
||||
{
|
||||
return bbox0;
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ __forceinline_odr GSVector4i::GSVector4i(const GSVector4& v, bool truncate)
|
||||
m = truncate ? _mm_cvttps_epi32(v) : _mm_cvtps_epi32(v);
|
||||
#elif defined(ARCH_ARM64)
|
||||
// GS thread uses default (nearest) rounding.
|
||||
v4s = truncate ? vcvtq_s32_f32(v.v4s) : vcvtnq_u32_f32(v.v4s);
|
||||
v4s = truncate ? vcvtq_s32_f32(v.v4s) : vreinterpretq_s32_u32(vcvtnq_u32_f32(v.v4s));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -703,17 +703,17 @@ public:
|
||||
|
||||
__forceinline GSVector4 mul64(const GSVector4& v) const
|
||||
{
|
||||
return GSVector4(vmulq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s)));
|
||||
return GSVector4(vreinterpretq_f32_f64(vmulq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4 add64(const GSVector4& v) const
|
||||
{
|
||||
return GSVector4(vaddq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s)));
|
||||
return GSVector4(vreinterpretq_f32_f64(vaddq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4 sub64(const GSVector4& v) const
|
||||
{
|
||||
return GSVector4(vsubq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s)));
|
||||
return GSVector4(vreinterpretq_f32_f64(vsubq_f64(vreinterpretq_f64_f32(v4s), vreinterpretq_f64_f32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline static GSVector4 f32to64(const GSVector4& v)
|
||||
@@ -728,10 +728,8 @@ public:
|
||||
|
||||
__forceinline GSVector4i f64toi32(bool truncate = true) const
|
||||
{
|
||||
const float64x2_t r = truncate ? v4s : vrndiq_f64(vreinterpretq_f64_f32(v4s));
|
||||
const s32 low = static_cast<s32>(vgetq_lane_f64(r, 0));
|
||||
const s32 high = static_cast<s32>(vgetq_lane_f64(r, 1));
|
||||
return GSVector4i(vsetq_lane_s32(high, vsetq_lane_s32(low, vdupq_n_s32(0), 0), 1));
|
||||
const int64x2_t r = truncate ? vcvtq_s64_f64(vreinterpretq_f64_f32(v4s)) : vcvtnq_s64_f64(vreinterpretq_f64_f32(v4s));
|
||||
return GSVector4i(vcombine_s32(vmovn_s64(r), vdup_n_s32(0)));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
@@ -782,6 +780,6 @@ public:
|
||||
|
||||
__forceinline static GSVector4 broadcast64(const void* f)
|
||||
{
|
||||
return GSVector4(vreinterpretq_f64_f32(vld1q_dup_f64((const double*)f)));
|
||||
return GSVector4(vreinterpretq_f32_f64(vld1q_dup_f64((const double*)f)));
|
||||
}
|
||||
};
|
||||
|
||||
+24
-24
@@ -373,7 +373,7 @@ public:
|
||||
|
||||
__forceinline u32 minv_u32() const
|
||||
{
|
||||
return vminvq_u32(v4s);
|
||||
return vminvq_u32(vreinterpretq_u32_s32(v4s));
|
||||
}
|
||||
|
||||
__forceinline u32 maxv_s32() const
|
||||
@@ -383,7 +383,7 @@ public:
|
||||
|
||||
__forceinline u32 maxv_u32() const
|
||||
{
|
||||
return vmaxvq_u32(v4s);
|
||||
return vmaxvq_u32(vreinterpretq_u32_s32(v4s));
|
||||
}
|
||||
|
||||
__forceinline static int min_i16(int a, int b)
|
||||
@@ -508,12 +508,12 @@ public:
|
||||
|
||||
__forceinline GSVector4i pkl32(const GSVector4i& a) const
|
||||
{
|
||||
return GSVector4i(vuzp1q_u32(v4s, a.v4s));
|
||||
return GSVector4i(vuzp1q_s32(v4s, a.v4s));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i pku32(const GSVector4i& a) const
|
||||
{
|
||||
return GSVector4i(vuzp2q_u32(v4s, a.v4s));
|
||||
return GSVector4i(vuzp2q_s32(v4s, a.v4s));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i upl8(const GSVector4i& a) const
|
||||
@@ -701,7 +701,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i srl16(s32 i) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_u16(vshlq_u16(vreinterpretq_u16_s32(v4s), vdupq_n_u16(-i))));
|
||||
return GSVector4i(vreinterpretq_s32_u16(vshlq_u16(vreinterpretq_u16_s32(v4s), vdupq_n_s16(-i))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i srlv16(const GSVector4i& v) const
|
||||
@@ -764,7 +764,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i srav32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vshlq_s32(vreinterpretq_u32_s32(v4s), vnegq_s32(v.v4s)));
|
||||
return GSVector4i(vshlq_s32(v4s, vnegq_s32(v.v4s)));
|
||||
}
|
||||
|
||||
template <int i>
|
||||
@@ -775,7 +775,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i sll64(s32 i) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vdupq_n_s16(i))));
|
||||
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vdupq_n_s64(i))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i sllv64(const GSVector4i& v) const
|
||||
@@ -791,7 +791,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i sra64(s32 i) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vdupq_n_s16(-i))));
|
||||
return GSVector4i(vreinterpretq_s32_s64(vshlq_s64(vreinterpretq_s64_s32(v4s), vdupq_n_s64(-i))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i srav64(const GSVector4i& v) const
|
||||
@@ -808,7 +808,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i srl64(s32 i) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_u64(vshlq_u64(vreinterpretq_u64_s32(v4s), vdupq_n_u16(-i))));
|
||||
return GSVector4i(vreinterpretq_s32_u64(vshlq_u64(vreinterpretq_u64_s32(v4s), vdupq_n_s64(-i))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i srlv64(const GSVector4i& v) const
|
||||
@@ -858,7 +858,7 @@ public:
|
||||
//return GSVector4i(vreinterpretq_s32_s16(vpaddq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
const int16x8_t a = vreinterpretq_s16_s32(v4s);
|
||||
const int16x8_t b = vreinterpretq_s16_s32(v.v4s);
|
||||
return GSVector4i(vqaddq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b)));
|
||||
return GSVector4i(vreinterpretq_s32_s16(vqaddq_s16(vuzp1q_s16(a, b), vuzp2q_s16(a, b))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i addus8(const GSVector4i& v) const
|
||||
@@ -888,7 +888,7 @@ public:
|
||||
|
||||
__forceinline GSVector4i hsub32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vsubq_u32(vuzp1q_u32(v4s, v.v4s), vuzp2q_u32(v4s, v.v4s)));
|
||||
return GSVector4i(vsubq_s32(vuzp1q_s32(v4s, v.v4s), vuzp2q_s32(v4s, v.v4s)));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i subs8(const GSVector4i& v) const
|
||||
@@ -1029,62 +1029,62 @@ public:
|
||||
|
||||
__forceinline GSVector4i gt8(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s8(vcgtq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u8(vcgtq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i gt16(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s16(vcgtq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u16(vcgtq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i gt32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vcgtq_s32(v4s, v.v4s));
|
||||
return GSVector4i(vreinterpretq_s32_u32(vcgtq_s32(v4s, v.v4s)));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i ge8(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s8(vcgeq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u8(vcgeq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i ge16(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s16(vcgeq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u16(vcgeq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i ge32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vcgeq_s32(v4s, v.v4s));
|
||||
return GSVector4i(vreinterpretq_s32_u32(vcgeq_s32(v4s, v.v4s)));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i lt8(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s8(vcltq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u8(vcltq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i lt16(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s16(vcltq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u16(vcltq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i lt32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vcltq_s32(v4s, v.v4s));
|
||||
return GSVector4i(vreinterpretq_s32_u32(vcltq_s32(v4s, v.v4s)));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i le8(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s8(vcleq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u8(vcleq_s8(vreinterpretq_s8_s32(v4s), vreinterpretq_s8_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i le16(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vreinterpretq_s32_s16(vcleq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
return GSVector4i(vreinterpretq_s32_u16(vcleq_s16(vreinterpretq_s16_s32(v4s), vreinterpretq_s16_s32(v.v4s))));
|
||||
}
|
||||
|
||||
__forceinline GSVector4i le32(const GSVector4i& v) const
|
||||
{
|
||||
return GSVector4i(vcleq_s32(v4s, v.v4s));
|
||||
return GSVector4i(vreinterpretq_s32_u32(vcleq_s32(v4s, v.v4s)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1112,7 +1112,7 @@ public:
|
||||
__forceinline bool allfalse() const
|
||||
{
|
||||
// MSB should be clear in all 8-bit lanes.
|
||||
return (vmaxvq_u32(vreinterpretq_u8_s32(v4s)) & 0x80) != 0x80;
|
||||
return (vmaxvq_u8(vreinterpretq_u8_s32(v4s)) & 0x80) != 0x80;
|
||||
}
|
||||
|
||||
template <int i>
|
||||
|
||||
@@ -1007,6 +1007,23 @@ bool GSDevice::ResizeRenderTarget(GSTexture** t, int w, int h, bool preserve_con
|
||||
return true;
|
||||
}
|
||||
|
||||
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->CreateRenderTarget(w, h, GSTexture::Format::Float32, false, true);
|
||||
const GSVector4 dRect(drawarea);
|
||||
const GSVector4 sRect(dRect.x / w, dRect.y / h, dRect.z / w, dRect.w / h);
|
||||
StretchRect(ds, sRect, m_ds_as_rt, dRect, ShaderConvert::FLOAT32_DEPTH_TO_COLOR, false);
|
||||
}
|
||||
|
||||
void GSDevice::EndDSAsRT()
|
||||
{
|
||||
Recycle(m_ds_as_rt);
|
||||
m_ds_as_rt = nullptr;
|
||||
}
|
||||
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-function"
|
||||
@@ -1150,10 +1167,12 @@ static const char* GetVSExpandName(GSHWDrawConfig::VSExpand vsexpand)
|
||||
{
|
||||
switch (vsexpand)
|
||||
{
|
||||
case GSHWDrawConfig::VSExpand::None: return "None";
|
||||
case GSHWDrawConfig::VSExpand::Point: return "Point";
|
||||
case GSHWDrawConfig::VSExpand::Line: return "Line";
|
||||
case GSHWDrawConfig::VSExpand::Sprite: return "Sprite";
|
||||
case GSHWDrawConfig::VSExpand::None: return "None";
|
||||
case GSHWDrawConfig::VSExpand::Point: return "Point";
|
||||
case GSHWDrawConfig::VSExpand::Line: return "Line";
|
||||
case GSHWDrawConfig::VSExpand::Sprite: return "Sprite";
|
||||
case GSHWDrawConfig::VSExpand::LineAA1: return "LineAA1";
|
||||
case GSHWDrawConfig::VSExpand::TriangleAA1: return "TriangleAA1";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
@@ -1354,6 +1373,7 @@ static const char* GetBlendFactorFormula(GSDevice::BlendFactor blendfactor)
|
||||
case GSDevice::CONST_ONE: return "1";
|
||||
case GSDevice::CONST_ZERO: return "0";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static const char* GetDestinationAlphaModeName(GSHWDrawConfig::DestinationAlphaMode datm)
|
||||
@@ -1394,6 +1414,18 @@ static const char* GetSetDATMName(SetDATM datm)
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static const char* GetPSAA1Name(u32 aa1)
|
||||
{
|
||||
switch (static_cast<GSHWDrawConfig::PS_AA1>(aa1))
|
||||
{
|
||||
case GSHWDrawConfig::PS_AA1::NONE: return "NONE";
|
||||
case GSHWDrawConfig::PS_AA1::LINE: return "LINE";
|
||||
case GSHWDrawConfig::PS_AA1::TRIANGLE: return "TRIANGLE";
|
||||
case GSHWDrawConfig::PS_AA1::TRIANGLE_SW_Z: return "TRIANGLE_SW_Z";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static void DumpPSSelector(DrawConfigWriter& out, const GSHWDrawConfig::PSSelector& ps)
|
||||
{
|
||||
out.WriteLn("aem_fmt: {}", ps.aem_fmt);
|
||||
@@ -1450,6 +1482,9 @@ static void DumpPSSelector(DrawConfigWriter& out, const GSHWDrawConfig::PSSelect
|
||||
out.WriteLn("point_sampler: {}", ps.point_sampler);
|
||||
out.WriteLn("region_rect: {}", ps.region_rect);
|
||||
out.WriteLn("scanmsk: {} ({})", GSUtil::GetSCANMSKName(ps.scanmsk), ps.scanmsk);
|
||||
out.WriteLn("aa1: {} ({})", static_cast<u32>(ps.aa1), GetPSAA1Name(static_cast<u32>(ps.aa1)));
|
||||
out.WriteLn("abe: {}", static_cast<u32>(ps.abe));
|
||||
out.WriteLn("sw_aniso: {}", ps.sw_aniso);
|
||||
}
|
||||
|
||||
static void DumpVSSelector(DrawConfigWriter& out, const GSHWDrawConfig::VSSelector& vs)
|
||||
@@ -1495,7 +1530,6 @@ static void DumpSamplerSelector(DrawConfigWriter& out, const GSHWDrawConfig::Sam
|
||||
out.WriteLn("tav: {}", ss.tav);
|
||||
out.WriteLn("biln: {}", ss.biln);
|
||||
out.WriteLn("triln: {} ({})", GetSSTrilnName(static_cast<GS_MIN_FILTER>(ss.triln)), ss.triln);
|
||||
out.WriteLn("aniso: {}", ss.aniso);
|
||||
out.WriteLn("lodclamp: {}", ss.lodclamp);
|
||||
}
|
||||
|
||||
@@ -1525,8 +1559,51 @@ static void DumpBlendMultipass(DrawConfigWriter& out, const GSHWDrawConfig::Blen
|
||||
DumpBlendState(out.WithIndent(), bmp.blend);
|
||||
}
|
||||
|
||||
template<typename T, typename U = int>
|
||||
static void DumpVector4(DrawConfigWriter& out, const char* name, const T& val)
|
||||
{
|
||||
out.WriteLn("{}: [{}, {}, {}, {}]", name, val.x, val.y, val.z, val.w);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
static void DumpVector2(DrawConfigWriter& out, const char* name, const T& val)
|
||||
{
|
||||
out.WriteLn("{}: [{}, {}]", name, val.x, val.y);
|
||||
};
|
||||
|
||||
static void DumpPSConstantBuffer(DrawConfigWriter& out, const GSHWDrawConfig::PSConstantBuffer& cb)
|
||||
{
|
||||
DumpVector4(out, "FogColor_AREF", cb.FogColor_AREF);
|
||||
DumpVector4(out, "WH", cb.WH);
|
||||
DumpVector4(out, "TA_MaxDepth_Af", cb.TA_MaxDepth_Af);
|
||||
DumpVector4(out, "FbMask", cb.FbMask);
|
||||
DumpVector4(out, "HalfTexel", cb.HalfTexel);
|
||||
DumpVector4(out, "MinMax", cb.MinMax);
|
||||
DumpVector4(out, "LODParams", cb.LODParams);
|
||||
DumpVector4(out, "STRange", cb.STRange);
|
||||
DumpVector4(out, "ChannelShuffle", cb.ChannelShuffle);
|
||||
DumpVector2(out, "ChannelShuffleOffset", cb.ChannelShuffleOffset);
|
||||
DumpVector2(out, "TCOffsetHack", cb.TCOffsetHack);
|
||||
DumpVector2(out, "STScale", cb.STScale);
|
||||
DumpVector4(out, "DitherMatrix_0", cb.DitherMatrix[0]);
|
||||
DumpVector4(out, "DitherMatrix_1", cb.DitherMatrix[1]);
|
||||
DumpVector4(out, "DitherMatrix_2", cb.DitherMatrix[2]);
|
||||
DumpVector4(out, "DitherMatrix_3", cb.DitherMatrix[3]);
|
||||
DumpVector4(out, "ScaleFactor", cb.ScaleFactor);
|
||||
}
|
||||
|
||||
static void DumpVSConstantBuffer(DrawConfigWriter& out, const GSHWDrawConfig::VSConstantBuffer& cb)
|
||||
{
|
||||
DumpVector2(out, "vertex_scale", cb.vertex_scale);
|
||||
DumpVector2(out, "vertex_offset", cb.vertex_offset);
|
||||
DumpVector2(out, "texture_scale", cb.texture_scale);
|
||||
DumpVector2(out, "texture_offset", cb.texture_offset);
|
||||
DumpVector2(out, "point_size", cb.point_size);
|
||||
DumpVector2(out, "max_depth", cb.max_depth);
|
||||
}
|
||||
|
||||
static void DumpConfig(DrawConfigWriter& out, const GSHWDrawConfig& conf,
|
||||
bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp)
|
||||
bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp, bool cbvs, bool cbps)
|
||||
{
|
||||
out.WriteLn("topology: {} ({})", GetTopologyName(conf.topology), static_cast<u32>(conf.topology));
|
||||
out.WriteLn("require_one_barrier: {}", conf.require_one_barrier);
|
||||
@@ -1578,15 +1655,27 @@ static void DumpConfig(DrawConfigWriter& out, const GSHWDrawConfig& conf,
|
||||
out.WriteLn("blend_multi_pass:");
|
||||
DumpBlendMultipass(out.WithIndent(), conf.blend_multi_pass);
|
||||
}
|
||||
|
||||
if (cbvs)
|
||||
{
|
||||
out.WriteLn("cb_vs:");
|
||||
DumpVSConstantBuffer(out.WithIndent(), conf.cb_vs);
|
||||
}
|
||||
|
||||
if (cbps)
|
||||
{
|
||||
out.WriteLn("cb_ps:");
|
||||
DumpPSConstantBuffer(out.WithIndent(), conf.cb_ps);
|
||||
}
|
||||
}
|
||||
|
||||
void GSHWDrawConfig::DumpConfig(const std::string& path, const GSHWDrawConfig& conf,
|
||||
bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp)
|
||||
bool ps, bool vs, bool bs, bool dss, bool ss, bool asp, bool bmp, bool cbvs, bool cbps)
|
||||
{
|
||||
if (FileSystem::ManagedCFilePtr file = FileSystem::OpenManagedCFile(path.c_str(), "w"))
|
||||
{
|
||||
DrawConfigWriter writer;
|
||||
::DumpConfig(writer, conf, ps, vs, bs, dss, ss, asp, bmp);
|
||||
::DumpConfig(writer, conf, ps, vs, bs, dss, ss, asp, bmp, cbvs, cbps);
|
||||
fwrite(writer.buffer.data(), 1, writer.buffer.size(), file.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +304,7 @@ struct alignas(16) GSHWDrawConfig
|
||||
using VSExpand = GSShader::VSExpand;
|
||||
using PS_ATST = GSShader::PS_ATST;
|
||||
using PS_AFAIL = GSShader::PS_AFAIL;
|
||||
using PS_AA1 = GSShader::PS_AA1;
|
||||
#pragma pack(push, 1)
|
||||
struct VSSelector
|
||||
{
|
||||
@@ -315,8 +316,8 @@ struct alignas(16) GSHWDrawConfig
|
||||
u8 tme : 1;
|
||||
u8 iip : 1;
|
||||
u8 point_size : 1; ///< Set when points need to be expanded without VS expanding.
|
||||
VSExpand expand : 2;
|
||||
u8 _free : 2;
|
||||
VSExpand expand : 3;
|
||||
u8 _free : 1;
|
||||
};
|
||||
u8 key;
|
||||
};
|
||||
@@ -324,7 +325,10 @@ struct alignas(16) GSHWDrawConfig
|
||||
VSSelector(u8 k): key(k) {}
|
||||
|
||||
/// Returns true if the fixed index buffer should be used.
|
||||
__fi bool UseExpandIndexBuffer() const { return (expand == VSExpand::Point || expand == VSExpand::Sprite); }
|
||||
__fi bool UseFixedExpandIndexBuffer() const { return (expand == VSExpand::Point || expand == VSExpand::Sprite); }
|
||||
|
||||
/// Return true if the index buffer should be bound as a vertex shader resource.
|
||||
__fi bool UseVSExpandIndexBuffer() const { return (expand == VSExpand::TriangleAA1); }
|
||||
};
|
||||
static_assert(sizeof(VSSelector) == 1, "VSSelector is a single byte");
|
||||
|
||||
@@ -415,6 +419,13 @@ struct alignas(16) GSHWDrawConfig
|
||||
|
||||
// Scan mask
|
||||
u32 scanmsk : 2;
|
||||
|
||||
// AA1
|
||||
PS_AA1 aa1 : 2; // Pixel shader AA1 primitive. Must be used in conjunction with VS AA1 expand.
|
||||
u32 abe : 1; // Alpha blend enabled. Currently only used for emulating AA1/ABE interaction.
|
||||
|
||||
// Anisotropic filtering
|
||||
u32 sw_aniso : 5;
|
||||
};
|
||||
|
||||
struct
|
||||
@@ -441,7 +452,8 @@ struct alignas(16) GSHWDrawConfig
|
||||
{
|
||||
const bool afail_needs_depth = afail == PS_AFAIL::FB_ONLY || afail == PS_AFAIL::RGB_ONLY_SW_Z;
|
||||
const bool ztst_needs_depth = ztst == ZTST_GEQUAL || ztst == ZTST_GREATER;
|
||||
return afail_needs_depth || ztst_needs_depth;
|
||||
const bool aa1_needs_depth = aa1 == PS_AA1::TRIANGLE_SW_Z;
|
||||
return afail_needs_depth || ztst_needs_depth || aa1_needs_depth;
|
||||
}
|
||||
|
||||
/// Disables color output from the pixel shader, this is done when all channels are masked.
|
||||
@@ -459,6 +471,20 @@ struct alignas(16) GSHWDrawConfig
|
||||
// disable both outputs.
|
||||
no_color = no_color1 = 1;
|
||||
}
|
||||
|
||||
/// Disables depth output from the pixel shader.
|
||||
__fi void DisableDepthOutput()
|
||||
{
|
||||
if (afail == PS_AFAIL::RGB_ONLY_SW_Z)
|
||||
{
|
||||
afail = PS_AFAIL::RGB_ONLY;
|
||||
}
|
||||
|
||||
if (aa1 == PS_AA1::TRIANGLE_SW_Z)
|
||||
{
|
||||
aa1 = PS_AA1::TRIANGLE;
|
||||
}
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(PSSelector) == 12, "PSSelector is 12 bytes");
|
||||
#pragma pack(pop)
|
||||
@@ -482,7 +508,6 @@ struct alignas(16) GSHWDrawConfig
|
||||
u8 tav : 1;
|
||||
u8 biln : 1;
|
||||
u8 triln : 3;
|
||||
u8 aniso : 1;
|
||||
u8 lodclamp : 1;
|
||||
};
|
||||
u8 key;
|
||||
@@ -618,6 +643,46 @@ struct alignas(16) GSHWDrawConfig
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct alignas(16) VSPushConstants
|
||||
{
|
||||
u32 base_vertex;
|
||||
u32 base_index;
|
||||
u32 _pad0;
|
||||
u32 _pad1;
|
||||
|
||||
__fi VSPushConstants()
|
||||
{
|
||||
memset(static_cast<void*>(this), 0, sizeof(*this));
|
||||
}
|
||||
__fi VSPushConstants(const VSPushConstants& other)
|
||||
{
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
}
|
||||
__fi VSPushConstants& operator=(const VSPushConstants& other)
|
||||
{
|
||||
new (this) VSPushConstants(other);
|
||||
return *this;
|
||||
}
|
||||
__fi bool operator==(const VSPushConstants& other) const
|
||||
{
|
||||
return BitEqual(*this, other);
|
||||
}
|
||||
__fi bool operator!=(const VSPushConstants& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
__fi bool Update(const VSPushConstants& other)
|
||||
{
|
||||
if (*this == other)
|
||||
return false;
|
||||
|
||||
memcpy(static_cast<void*>(this), static_cast<const void*>(&other), sizeof(*this));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(VSPushConstants) == 16, "VSPushConstants wrong size");
|
||||
|
||||
struct alignas(16) PSConstantBuffer
|
||||
{
|
||||
GSVector4 FogColor_AREF;
|
||||
@@ -747,7 +812,6 @@ struct alignas(16) GSHWDrawConfig
|
||||
};
|
||||
|
||||
GSTexture* rt; ///< Render target
|
||||
GSTexture* ds_as_rt; ///< Depth as color (if supported)
|
||||
GSTexture* ds; ///< Depth stencil
|
||||
GSTexture* tex; ///< Source texture
|
||||
GSTexture* pal; ///< Palette texture
|
||||
@@ -776,8 +840,8 @@ struct alignas(16) GSHWDrawConfig
|
||||
AlphaTestMode alpha_test;
|
||||
|
||||
DestinationAlphaMode destination_alpha;
|
||||
SetDATM datm : 2;
|
||||
bool line_expand : 1;
|
||||
SetDATM datm;
|
||||
bool line_expand;
|
||||
|
||||
struct AlphaPass
|
||||
{
|
||||
@@ -815,7 +879,8 @@ struct alignas(16) GSHWDrawConfig
|
||||
|
||||
// Dumping
|
||||
static void DumpConfig(const std::string& path, const GSHWDrawConfig& conf,
|
||||
bool ps = true, bool vs = true, bool bs = true, bool dss = true, bool ss = true, bool asp = true, bool bmp = true);
|
||||
bool ps = true, bool vs = true, bool bs = true, bool dss = true, bool ss = true, bool asp = true, bool bmp = true,
|
||||
bool cbvs = true, bool cbps = true);
|
||||
};
|
||||
|
||||
static inline u32 GetExpansionFactor(GSHWDrawConfig::VSExpand expand)
|
||||
@@ -824,9 +889,12 @@ static inline u32 GetExpansionFactor(GSHWDrawConfig::VSExpand expand)
|
||||
{
|
||||
case GSHWDrawConfig::VSExpand::Point:
|
||||
case GSHWDrawConfig::VSExpand::Line:
|
||||
case GSHWDrawConfig::VSExpand::LineAA1:
|
||||
return 4;
|
||||
case GSHWDrawConfig::VSExpand::Sprite:
|
||||
return 2;
|
||||
case GSHWDrawConfig::VSExpand::TriangleAA1:
|
||||
return 7;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
@@ -863,13 +931,6 @@ public:
|
||||
Performance
|
||||
};
|
||||
|
||||
enum class DepthFeedbackSupport : u8
|
||||
{
|
||||
None, // No support for depth feedback loops.
|
||||
Depth, // Implement depth feedback loops directly on the depth buffer.
|
||||
DepthAsRT, // Implement depth feedback loops by first converting depth to a color RT.
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
struct FeatureSupport
|
||||
{
|
||||
@@ -888,11 +949,14 @@ public:
|
||||
bool stencil_buffer : 1; ///< Supports stencil buffer, and can use for DATE.
|
||||
bool cas_sharpening : 1; ///< Supports sufficient functionality for contrast adaptive sharpening.
|
||||
bool test_and_sample_depth: 1; ///< Supports concurrently binding the depth-stencil buffer for sampling and depth testing.
|
||||
DepthFeedbackSupport depth_feedback : 2; ///< Support for depth feedback loops.
|
||||
bool depth_feedback : 1; ///< Depth feedback loops can be done with DS directly (otherwise need to copy to separate RT). Implies `feedback_loops`.
|
||||
bool aa1 : 1; ///< Supports the GS AA1 feature.
|
||||
FeatureSupport()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
/// Supports feedback loops through either texture barriers or rt copies.
|
||||
bool feedback_loops() const { return texture_barrier || multidraw_fb_copy; }
|
||||
};
|
||||
|
||||
struct MultiStretchRect
|
||||
@@ -970,6 +1034,7 @@ protected:
|
||||
GSTexture* m_current = nullptr;
|
||||
GSTexture* m_cas = nullptr;
|
||||
GSTexture* m_colclip_rt = nullptr; ///< Temp hw colclip texture
|
||||
GSTexture* m_ds_as_rt = nullptr; ///< Depth as color
|
||||
|
||||
bool AcquireWindow(bool recreate_window);
|
||||
|
||||
@@ -1007,6 +1072,11 @@ public:
|
||||
|
||||
void SetColorClipTexture(GSTexture* tex) { m_colclip_rt = tex; }
|
||||
|
||||
bool IsDSInRTActive() const { return m_ds_as_rt; }
|
||||
/// Create a temporary color clone of depth for depth feedback
|
||||
virtual void BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea);
|
||||
void EndDSAsRT();
|
||||
|
||||
/// Returns a string representing the specified API.
|
||||
static const char* RenderAPIToString(RenderAPI api);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "GSDirtyRect.h"
|
||||
#include <climits>
|
||||
#include <vector>
|
||||
|
||||
GSDirtyRect::GSDirtyRect() :
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/AlignedMalloc.h"
|
||||
#include <climits>
|
||||
|
||||
template <class T>
|
||||
struct Element
|
||||
|
||||
@@ -718,7 +718,7 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
const GSTexture* current = g_gs_device->GetCurrent();
|
||||
const GSVector2i internal_res = GetInternalResolution();
|
||||
|
||||
if (current->GetWidth() > internal_res.x || current->GetHeight() > internal_res.y)
|
||||
if (current && (current->GetWidth() > internal_res.x || current->GetHeight() > internal_res.y))
|
||||
g_gs_device->Resize(internal_res.x, internal_res.y);
|
||||
}
|
||||
|
||||
@@ -840,6 +840,9 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()) && GSConfig.SaveTransferImages)
|
||||
DumpTransferImages();
|
||||
}
|
||||
|
||||
void GSRenderer::QueueSnapshot(const std::string& path, const u32 gsdump_frames)
|
||||
|
||||
@@ -11,10 +11,12 @@ namespace GSShader {
|
||||
|
||||
enum class VSExpand : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
Point = 1,
|
||||
Line = 2,
|
||||
Sprite = 3,
|
||||
None = 0,
|
||||
Point = 1,
|
||||
Line = 2,
|
||||
Sprite = 3,
|
||||
LineAA1 = 4,
|
||||
TriangleAA1 = 5,
|
||||
};
|
||||
|
||||
enum class PS_ATST : uint32_t
|
||||
@@ -37,4 +39,21 @@ enum class PS_AFAIL : uint32_t
|
||||
RGB_ONLY_SW_Z = 5, ///< RGB only with software Z discard
|
||||
};
|
||||
|
||||
// Identical to GS_ZTST
|
||||
enum class ZTST : uint32_t
|
||||
{
|
||||
NEVER = 0,
|
||||
ALWAYS = 1,
|
||||
GEQUAL = 2,
|
||||
GREATER = 3,
|
||||
};
|
||||
|
||||
enum class PS_AA1 : uint32_t
|
||||
{
|
||||
NONE = 0, ///< No AA1
|
||||
LINE = 1, ///< AA1 lines
|
||||
TRIANGLE = 2, ///< AA1 triangles
|
||||
TRIANGLE_SW_Z = 3, ///< AA1 triangles with software Z discard
|
||||
};
|
||||
|
||||
} // namespace GSShader
|
||||
|
||||
@@ -30,6 +30,10 @@ public:
|
||||
struct VertexAlpha
|
||||
{
|
||||
int min, max;
|
||||
|
||||
// Separate inference for depth if using AA1 coverage alpha, since edges don't write depth.
|
||||
int depth_min, depth_max;
|
||||
|
||||
bool valid;
|
||||
};
|
||||
bool m_accurate_stq = false;
|
||||
|
||||
@@ -466,28 +466,52 @@ GSRendererType D3D::GetPreferredRenderer()
|
||||
}
|
||||
}
|
||||
|
||||
wil::com_ptr_nothrow<ID3DBlob> D3D::CompileShader(D3D::ShaderType type, D3D_FEATURE_LEVEL feature_level, bool debug,
|
||||
const char* D3D::ShaderModelToCacheString(D3D::ShaderModel shader_model)
|
||||
{
|
||||
switch (shader_model)
|
||||
{
|
||||
case ShaderModel::SM40:
|
||||
return "sm40";
|
||||
case ShaderModel::SM41:
|
||||
return "sm41";
|
||||
case ShaderModel::SM50:
|
||||
return "sm50";
|
||||
case ShaderModel::SM51:
|
||||
return "sm51";
|
||||
default:
|
||||
return "unk";
|
||||
}
|
||||
}
|
||||
|
||||
wil::com_ptr_nothrow<ID3DBlob> D3D::CompileShader(D3D::ShaderType type, D3D::ShaderModel shader_model, bool debug,
|
||||
const std::string_view code, const D3D_SHADER_MACRO* macros /* = nullptr */,
|
||||
const char* entry_point /* = "main" */)
|
||||
{
|
||||
const char* target;
|
||||
switch (feature_level)
|
||||
switch (shader_model)
|
||||
{
|
||||
case D3D_FEATURE_LEVEL_10_0:
|
||||
case ShaderModel::SM40:
|
||||
{
|
||||
static constexpr std::array<const char*, 4> targets = {{"vs_4_0", "ps_4_0", "cs_4_0"}};
|
||||
target = targets[static_cast<int>(type)];
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D_FEATURE_LEVEL_11_0:
|
||||
case ShaderModel::SM41:
|
||||
{
|
||||
static constexpr std::array<const char*, 4> targets = {{"vs_4_1", "ps_4_1", "cs_4_1"}};
|
||||
target = targets[static_cast<int>(type)];
|
||||
}
|
||||
break;
|
||||
|
||||
case ShaderModel::SM50:
|
||||
{
|
||||
static constexpr std::array<const char*, 4> targets = {{"vs_5_0", "ps_5_0", "cs_5_0"}};
|
||||
target = targets[static_cast<int>(type)];
|
||||
}
|
||||
break;
|
||||
|
||||
case D3D_FEATURE_LEVEL_11_1:
|
||||
case ShaderModel::SM51:
|
||||
default:
|
||||
{
|
||||
static constexpr std::array<const char*, 4> targets = {{"vs_5_1", "ps_5_1", "cs_5_1"}};
|
||||
@@ -496,8 +520,8 @@ wil::com_ptr_nothrow<ID3DBlob> D3D::CompileShader(D3D::ShaderType type, D3D_FEAT
|
||||
break;
|
||||
}
|
||||
|
||||
static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
||||
static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE;
|
||||
static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_IEEE_STRICTNESS;
|
||||
static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG | D3DCOMPILE_DEBUG_NAME_FOR_SOURCE | D3DCOMPILE_IEEE_STRICTNESS;
|
||||
|
||||
wil::com_ptr_nothrow<ID3DBlob> blob;
|
||||
wil::com_ptr_nothrow<ID3DBlob> error_blob;
|
||||
|
||||
@@ -63,6 +63,16 @@ namespace D3D
|
||||
Compute
|
||||
};
|
||||
|
||||
wil::com_ptr_nothrow<ID3DBlob> CompileShader(ShaderType type, D3D_FEATURE_LEVEL feature_level, bool debug,
|
||||
enum class ShaderModel
|
||||
{
|
||||
SM40 = 0x40, // DX11 FL 10_0
|
||||
SM41 = 0x41, // DX11 FL 10_1
|
||||
SM50 = 0x50, // DX11 FL 11_0
|
||||
SM51 = 0x51, // DX12
|
||||
};
|
||||
|
||||
const char* ShaderModelToCacheString(ShaderModel shader_model);
|
||||
|
||||
wil::com_ptr_nothrow<ID3DBlob> CompileShader(ShaderType type, ShaderModel shader_model, bool debug,
|
||||
const std::string_view code, const D3D_SHADER_MACRO* macros = nullptr, const char* entry_point = "main");
|
||||
}; // namespace D3D
|
||||
|
||||
@@ -56,12 +56,27 @@ bool D3D11ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const
|
||||
|
||||
bool D3D11ShaderCache::Open(D3D_FEATURE_LEVEL feature_level, bool debug)
|
||||
{
|
||||
m_feature_level = feature_level;
|
||||
switch (feature_level)
|
||||
{
|
||||
case D3D_FEATURE_LEVEL_10_0:
|
||||
m_shader_model = D3D::ShaderModel::SM40;
|
||||
break;
|
||||
case D3D_FEATURE_LEVEL_10_1:
|
||||
m_shader_model = D3D::ShaderModel::SM41;
|
||||
break;
|
||||
case D3D_FEATURE_LEVEL_11_0:
|
||||
m_shader_model = D3D::ShaderModel::SM50;
|
||||
break;
|
||||
default:
|
||||
pxAssert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_debug = debug;
|
||||
|
||||
if (!GSConfig.DisableShaderCache)
|
||||
{
|
||||
const std::string base_filename = GetCacheBaseFileName(feature_level, debug);
|
||||
const std::string base_filename = GetCacheBaseFileName(m_shader_model, debug);
|
||||
const std::string index_filename = base_filename + ".idx";
|
||||
const std::string blob_filename = base_filename + ".bin";
|
||||
|
||||
@@ -198,19 +213,10 @@ bool D3D11ShaderCache::ReadExisting(const std::string& index_filename, const std
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string D3D11ShaderCache::GetCacheBaseFileName(D3D_FEATURE_LEVEL feature_level, bool debug)
|
||||
std::string D3D11ShaderCache::GetCacheBaseFileName(D3D::ShaderModel shader_model, bool debug)
|
||||
{
|
||||
std::string base_filename = "d3d_shaders_";
|
||||
|
||||
switch (feature_level)
|
||||
{
|
||||
case D3D_FEATURE_LEVEL_11_0:
|
||||
base_filename += "sm50";
|
||||
break;
|
||||
default:
|
||||
base_filename += "unk";
|
||||
break;
|
||||
}
|
||||
base_filename += D3D::ShaderModelToCacheString(shader_model);
|
||||
|
||||
if (debug)
|
||||
base_filename += "_debug";
|
||||
@@ -400,7 +406,7 @@ wil::com_ptr_nothrow<ID3DBlob> D3D11ShaderCache::CompileAndAddShaderBlob(const C
|
||||
const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point)
|
||||
{
|
||||
wil::com_ptr_nothrow<ID3DBlob> blob =
|
||||
D3D::CompileShader(key.shader_type, m_feature_level, m_debug, shader_code, macros, entry_point);
|
||||
D3D::CompileShader(key.shader_type, m_shader_model, m_debug, shader_code, macros, entry_point);
|
||||
if (!blob)
|
||||
return {};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ public:
|
||||
D3D11ShaderCache();
|
||||
~D3D11ShaderCache();
|
||||
|
||||
D3D_FEATURE_LEVEL GetFeatureLevel() const { return m_feature_level; }
|
||||
bool UsingDebugShaders() const { return m_debug; }
|
||||
|
||||
bool Open(D3D_FEATURE_LEVEL feature_level, bool debug);
|
||||
@@ -74,7 +73,7 @@ private:
|
||||
|
||||
using CacheIndex = std::unordered_map<CacheIndexKey, CacheIndexData, CacheIndexEntryHasher>;
|
||||
|
||||
static std::string GetCacheBaseFileName(D3D_FEATURE_LEVEL feature_level, bool debug);
|
||||
static std::string GetCacheBaseFileName(D3D::ShaderModel shader_mode, bool debug);
|
||||
static CacheIndexKey GetCacheKey(D3D::ShaderType type, const std::string_view shader_code,
|
||||
const D3D_SHADER_MACRO* macros, const char* entry_point);
|
||||
|
||||
@@ -89,6 +88,6 @@ private:
|
||||
|
||||
CacheIndex m_index;
|
||||
|
||||
D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0;
|
||||
D3D::ShaderModel m_shader_model = D3D::ShaderModel::SM50;
|
||||
bool m_debug = false;
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ GSDevice11::GSDevice11()
|
||||
|
||||
m_features.primitive_id = true;
|
||||
m_features.texture_barrier = false;
|
||||
m_features.multidraw_fb_copy = GSConfig.OverrideTextureBarriers != 0;
|
||||
m_features.multidraw_fb_copy = false;
|
||||
m_features.provoking_vertex_last = false;
|
||||
m_features.point_expand = false;
|
||||
m_features.line_expand = false;
|
||||
@@ -68,15 +68,8 @@ GSDevice11::GSDevice11()
|
||||
m_features.stencil_buffer = true;
|
||||
m_features.cas_sharpening = true;
|
||||
m_features.test_and_sample_depth = false;
|
||||
if (m_features.multidraw_fb_copy && (GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto ||
|
||||
GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth))
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
}
|
||||
m_features.depth_feedback = false;
|
||||
m_features.aa1 = false;
|
||||
}
|
||||
|
||||
GSDevice11::~GSDevice11() = default;
|
||||
@@ -414,6 +407,30 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
}
|
||||
}
|
||||
|
||||
if (m_features.aa1)
|
||||
{
|
||||
bd.ByteWidth = INDEX_BUFFER_SIZE;
|
||||
bd.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
bd.StructureByteStride = sizeof(u32);
|
||||
bd.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
|
||||
|
||||
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_expand_ib_vs.put())))
|
||||
{
|
||||
Console.Error("D3D11: Failed to create vertex shader index buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const CD3D11_SHADER_RESOURCE_VIEW_DESC expand_ib_vs_srv_desc(
|
||||
D3D11_SRV_DIMENSION_BUFFER, DXGI_FORMAT_UNKNOWN, 0, INDEX_BUFFER_SIZE / sizeof(u32));
|
||||
if (FAILED(m_dev->CreateShaderResourceView(m_expand_ib_vs.get(), &expand_ib_vs_srv_desc, m_expand_ib_vs_srv.put())))
|
||||
{
|
||||
Console.Error("D3D11: Failed to create vertex shader index buffer SRV.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ctx->VSSetShaderResources(5, 1, m_expand_ib_vs_srv.addressof());
|
||||
}
|
||||
|
||||
// rasterizer
|
||||
|
||||
memset(&rd, 0, sizeof(rd));
|
||||
@@ -467,6 +484,18 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
|
||||
memset(&bd, 0, sizeof(bd));
|
||||
|
||||
bd.ByteWidth = sizeof(GSHWDrawConfig::VSConstantBuffer);
|
||||
bd.Usage = D3D11_USAGE_DEFAULT;
|
||||
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
|
||||
if (FAILED(m_dev->CreateBuffer(&bd, nullptr, m_vs_pc.put())))
|
||||
{
|
||||
Console.Error("D3D11: Failed to create vertex shader push constant buffer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&bd, 0, sizeof(bd));
|
||||
|
||||
bd.ByteWidth = sizeof(GSHWDrawConfig::PSConstantBuffer);
|
||||
bd.Usage = D3D11_USAGE_DEFAULT;
|
||||
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
@@ -477,6 +506,8 @@ bool GSDevice11::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
return false;
|
||||
}
|
||||
|
||||
VSSetPushConstants(0, 0, true); // Avoid undefined data.
|
||||
|
||||
// create layout
|
||||
|
||||
{
|
||||
@@ -563,6 +594,7 @@ void GSDevice11::Destroy()
|
||||
|
||||
m_vs.clear();
|
||||
m_vs_cb.reset();
|
||||
m_vs_pc.reset();
|
||||
m_gs.clear();
|
||||
m_ps.clear();
|
||||
m_ps_cb.reset();
|
||||
@@ -605,16 +637,20 @@ void GSDevice11::Destroy()
|
||||
|
||||
void GSDevice11::SetFeatures(IDXGIAdapter1* adapter)
|
||||
{
|
||||
// Check these first as others depend on them.
|
||||
m_features.multidraw_fb_copy = GSConfig.OverrideTextureBarriers != 0;
|
||||
m_features.vs_expand = (!GSConfig.DisableVertexShaderExpand && m_feature_level >= D3D_FEATURE_LEVEL_11_0);
|
||||
|
||||
// Check all three formats, since the feature means any can be used.
|
||||
m_features.dxt_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC1_UNORM) &&
|
||||
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC2_UNORM) &&
|
||||
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC3_UNORM);
|
||||
|
||||
m_features.bptc_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC7_UNORM);
|
||||
|
||||
m_features.vs_expand = (!GSConfig.DisableVertexShaderExpand && m_feature_level >= D3D_FEATURE_LEVEL_11_0);
|
||||
m_features.cas_sharpening = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
|
||||
m_features.test_and_sample_depth = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
|
||||
m_features.depth_feedback = m_features.feedback_loops();
|
||||
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
|
||||
|
||||
m_max_texture_size = (m_feature_level >= D3D_FEATURE_LEVEL_11_0) ?
|
||||
D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION :
|
||||
@@ -1003,7 +1039,7 @@ void GSDevice11::EndPresent()
|
||||
PopTimestampQuery();
|
||||
|
||||
// clear out the swap chain view, it might get resized..
|
||||
OMSetRenderTargets(nullptr, nullptr, nullptr, nullptr);
|
||||
OMSetRenderTargets(nullptr, nullptr);
|
||||
|
||||
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GSVSyncMode::FIFO);
|
||||
const UINT flags = (m_vsync_mode == GSVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;
|
||||
@@ -1140,9 +1176,7 @@ void GSDevice11::DrawPrimitive()
|
||||
|
||||
void GSDevice11::DrawIndexedPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
PSUpdateShaderState(true, true);
|
||||
m_ctx->DrawIndexed(m_index.count, m_index.start, m_vertex.start);
|
||||
DrawIndexedPrimitive(0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDevice11::DrawIndexedPrimitive(int offset, int count)
|
||||
@@ -1153,6 +1187,43 @@ void GSDevice11::DrawIndexedPrimitive(int offset, int count)
|
||||
m_ctx->DrawIndexed(count, m_index.start + offset, m_vertex.start);
|
||||
}
|
||||
|
||||
void GSDevice11::DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion)
|
||||
{
|
||||
pxAssert(offset + count <= (int)m_index.count);
|
||||
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
PSUpdateShaderState(true, true);
|
||||
if (vs_indexing)
|
||||
{
|
||||
VSSetPushConstants(m_vertex.start, m_index.start + offset);
|
||||
m_ctx->Draw(count * vs_indexing_expansion, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
VSSetPushConstants(m_vertex.start);
|
||||
m_ctx->DrawIndexed(count, m_index.start + offset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::Draw(const GSHWDrawConfig& config, int offset, int count)
|
||||
{
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
{
|
||||
const bool vs_indexing = config.vs.UseVSExpandIndexBuffer();
|
||||
const u32 vs_indexing_expansion = GetExpansionFactor(config.vs.expand);
|
||||
DrawIndexedPrimitiveVSExpand(offset, count, vs_indexing, vs_indexing_expansion);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawIndexedPrimitive(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::Draw(const GSHWDrawConfig& config)
|
||||
{
|
||||
Draw(config, 0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDevice11::CommitClear(GSTexture* t)
|
||||
{
|
||||
GSTexture11* T = static_cast<GSTexture11*>(t);
|
||||
@@ -1335,13 +1406,6 @@ void GSDevice11::DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTextur
|
||||
GSVector2i ds;
|
||||
if (dTex)
|
||||
{
|
||||
// preemptively bind srv if possible
|
||||
if (m_state.current_rt != sTex && m_state.current_ds != sTex)
|
||||
PSSetShaderResource(0, sTex);
|
||||
|
||||
// ps unbind conflicting srvs
|
||||
PSUnbindConflictingSRVs(dTex);
|
||||
|
||||
ds = dTex->GetSize();
|
||||
if (draw_in_depth)
|
||||
OMSetRenderTargets(nullptr, dTex);
|
||||
@@ -1409,13 +1473,6 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
|
||||
GSVector2i ds;
|
||||
if (dTex)
|
||||
{
|
||||
// preemptively bind srv if possible
|
||||
if (m_state.current_rt != sTex && m_state.current_ds != sTex)
|
||||
PSSetShaderResource(0, sTex);
|
||||
|
||||
// ps unbind conflicting srvs
|
||||
PSUnbindConflictingSRVs(dTex);
|
||||
|
||||
ds = dTex->GetSize();
|
||||
OMSetRenderTargets(dTex, nullptr);
|
||||
}
|
||||
@@ -1536,7 +1593,6 @@ void GSDevice11::DrawMultiStretchRects(const MultiStretchRect* rects, u32 num_re
|
||||
|
||||
VSSetShader(m_convert.vs.get(), nullptr);
|
||||
PSSetShader(m_convert.ps[static_cast<int>(shader)].get(), nullptr);
|
||||
PSUnbindConflictingSRVs(dTex);
|
||||
|
||||
OMSetDepthStencilState(dTex->IsRenderTarget() ? m_convert.dss.get() : m_convert.dss_write.get(), 0);
|
||||
OMSetRenderTargets(dTex->IsRenderTarget() ? dTex : nullptr, dTex->IsDepthStencil() ? dTex : nullptr);
|
||||
@@ -1833,6 +1889,9 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
sm.AddMacro("PS_NO_COLOR", sel.no_color);
|
||||
sm.AddMacro("PS_NO_COLOR1", sel.no_color1);
|
||||
sm.AddMacro("PS_ZTST", sel.ztst);
|
||||
sm.AddMacro("PS_AA1", static_cast<u32>(sel.aa1));
|
||||
sm.AddMacro("PS_ABE", sel.abe);
|
||||
sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso);
|
||||
|
||||
wil::com_ptr_nothrow<ID3D11PixelShader> ps = m_shader_cache.GetPixelShader(m_dev.get(), m_tfx_source, sm.GetPtr(), "ps_main");
|
||||
i = m_ps.try_emplace(sel, std::move(ps)).first;
|
||||
@@ -1860,39 +1919,33 @@ void GSDevice11::SetupPS(const PSSelector& sel, const GSHWDrawConfig::PSConstant
|
||||
}
|
||||
else
|
||||
{
|
||||
D3D11_SAMPLER_DESC sd = {};
|
||||
static constexpr std::array<D3D11_FILTER, 8> filters = {{
|
||||
D3D11_FILTER_MIN_MAG_MIP_POINT, // 000 / min=point,mag=point,mip=point
|
||||
D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT, // 001 / min=linear,mag=point,mip=point
|
||||
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, // 010 / min=point,mag=linear,mip=point
|
||||
D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT, // 011 / min=linear,mag=linear,mip=point
|
||||
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR, // 100 / min=point,mag=point,mip=linear
|
||||
D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, // 101 / min=linear,mag=point,mip=linear
|
||||
D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR, // 110 / min=point,mag=linear,mip=linear
|
||||
D3D11_FILTER_MIN_MAG_MIP_LINEAR, // 111 / min=linear,mag=linear,mip=linear
|
||||
}};
|
||||
|
||||
const int anisotropy = GSConfig.MaxAnisotropy;
|
||||
if (anisotropy > 1 && ssel.aniso)
|
||||
{
|
||||
sd.Filter = D3D11_FILTER_ANISOTROPIC;
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::array<D3D11_FILTER, 8> filters = {{
|
||||
D3D11_FILTER_MIN_MAG_MIP_POINT, // 000 / min=point,mag=point,mip=point
|
||||
D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT, // 001 / min=linear,mag=point,mip=point
|
||||
D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, // 010 / min=point,mag=linear,mip=point
|
||||
D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT, // 011 / min=linear,mag=linear,mip=point
|
||||
D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR, // 100 / min=point,mag=point,mip=linear
|
||||
D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, // 101 / min=linear,mag=point,mip=linear
|
||||
D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR, // 110 / min=point,mag=linear,mip=linear
|
||||
D3D11_FILTER_MIN_MAG_MIP_LINEAR, // 111 / min=linear,mag=linear,mip=linear
|
||||
}};
|
||||
const u8 index = (static_cast<u8>(ssel.IsMipFilterLinear()) << 2) |
|
||||
(static_cast<u8>(ssel.IsMagFilterLinear()) << 1) |
|
||||
static_cast<u8>(ssel.IsMinFilterLinear());
|
||||
|
||||
const u8 index = (static_cast<u8>(ssel.IsMipFilterLinear()) << 2) |
|
||||
(static_cast<u8>(ssel.IsMagFilterLinear()) << 1) |
|
||||
static_cast<u8>(ssel.IsMinFilterLinear());
|
||||
sd.Filter = filters[index];
|
||||
}
|
||||
|
||||
sd.AddressU = ssel.tau ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sd.AddressV = ssel.tav ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sd.MinLOD = 0.0f;
|
||||
sd.MaxLOD = (ssel.lodclamp || !ssel.UseMipmapFiltering()) ? 0.25f : FLT_MAX;
|
||||
sd.MaxAnisotropy = std::clamp(anisotropy, 1, 16);
|
||||
sd.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
||||
const D3D11_SAMPLER_DESC sd = {
|
||||
filters[index], // Filter
|
||||
ssel.tau ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP, // Address u
|
||||
ssel.tav ? D3D11_TEXTURE_ADDRESS_WRAP : D3D11_TEXTURE_ADDRESS_CLAMP, // Address v
|
||||
D3D11_TEXTURE_ADDRESS_CLAMP, // Address w
|
||||
0.0f, // Lod bias
|
||||
1, // Anisotropy
|
||||
D3D11_COMPARISON_NEVER, // comparison function
|
||||
{}, // Border colour
|
||||
0.0f, // Min lod
|
||||
(ssel.lodclamp || !ssel.UseMipmapFiltering()) ? 0.25f : FLT_MAX, // Max lod
|
||||
};
|
||||
|
||||
m_dev->CreateSamplerState(&sd, &ss0);
|
||||
|
||||
@@ -2032,13 +2085,13 @@ bool GSDevice11::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, cons
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
|
||||
static const int threadGroupWorkRegionDim = 16;
|
||||
static constexpr int threadGroupWorkRegionDim = 16;
|
||||
const int dispatchX = (dTex->GetWidth() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
const int dispatchY = (dTex->GetHeight() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
ID3D11ShaderResourceView* srvs[1] = {*static_cast<GSTexture11*>(sTex)};
|
||||
ID3D11UnorderedAccessView* uavs[1] = {*static_cast<GSTexture11*>(dTex)};
|
||||
m_ctx->OMSetRenderTargets(0, nullptr, nullptr);
|
||||
OMSetRenderTargets(nullptr, nullptr);
|
||||
m_ctx->UpdateSubresource(m_cas.cb.get(), 0, nullptr, constants.data(), 0, 0);
|
||||
m_ctx->CSSetConstantBuffers(0, 1, m_cas.cb.addressof());
|
||||
m_ctx->CSSetShader(sharpen_only ? m_cas.cs_sharpen.get() : m_cas.cs_upscale.get(), nullptr, 0);
|
||||
@@ -2228,15 +2281,6 @@ void GSDevice11::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSV
|
||||
|
||||
m_ctx->ClearDepthStencilView(*static_cast<GSTexture11*>(ds), D3D11_CLEAR_STENCIL, 0.0f, 0);
|
||||
|
||||
// preemptively bind srv if possible
|
||||
|
||||
if (m_state.current_rt != rt && m_state.current_ds != rt)
|
||||
PSSetShaderResource(0, rt);
|
||||
|
||||
// ps unbind conflicting srvs
|
||||
|
||||
PSUnbindConflictingSRVs(ds);
|
||||
|
||||
// om
|
||||
|
||||
OMSetDepthStencilState(m_date.dss.get(), 1);
|
||||
@@ -2303,12 +2347,7 @@ void GSDevice11::IAUnmapVertexBuffer(u32 stride, u32 count)
|
||||
{
|
||||
m_ctx->Unmap(m_vb.get(), 0);
|
||||
|
||||
if (m_state.vb_stride != stride)
|
||||
{
|
||||
m_state.vb_stride = stride;
|
||||
const UINT vb_offset = 0;
|
||||
m_ctx->IASetVertexBuffers(0, 1, m_vb.addressof(), &stride, &vb_offset);
|
||||
}
|
||||
IASetVertexBuffer(m_vb.get(), stride);
|
||||
|
||||
m_vertex.count = count;
|
||||
}
|
||||
@@ -2325,6 +2364,16 @@ bool GSDevice11::IASetVertexBuffer(const void* vertex, u32 stride, u32 count)
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::IASetVertexBuffer(ID3D11Buffer* buffer, u32 stride)
|
||||
{
|
||||
if (m_state.vb != buffer || stride != m_state.vb_stride)
|
||||
{
|
||||
const UINT zero = 0;
|
||||
m_ctx->IASetVertexBuffers(0, 1, &buffer, buffer ? &stride : &zero, &zero);
|
||||
m_state.vb = buffer;
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDevice11::IASetExpandVertexBuffer(const void* vertex, u32 stride, u32 count)
|
||||
{
|
||||
const u32 size = stride * count;
|
||||
@@ -2444,10 +2493,73 @@ void GSDevice11::VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb)
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice11::VSSetPushConstants(u32 base_vertex, u32 base_index, bool force_update)
|
||||
{
|
||||
GSHWDrawConfig::VSPushConstants vs_pc;
|
||||
vs_pc.base_vertex = base_vertex;
|
||||
vs_pc.base_index = base_index;
|
||||
|
||||
if (m_vs_pc_cache.Update(vs_pc) || force_update)
|
||||
{
|
||||
m_ctx->UpdateSubresource(m_vs_pc.get(), 0, NULL, &vs_pc, 0, 0);
|
||||
}
|
||||
|
||||
if (m_state.vs_pc != m_vs_pc.get())
|
||||
{
|
||||
m_state.vs_pc = m_vs_pc.get();
|
||||
|
||||
m_ctx->VSSetConstantBuffers(1, 1, &m_state.vs_pc);
|
||||
}
|
||||
}
|
||||
|
||||
u16* GSDevice11::VSMapIndexBuffer(u32 count)
|
||||
{
|
||||
if (count > (INDEX_BUFFER_SIZE / sizeof(u16)))
|
||||
return nullptr;
|
||||
|
||||
D3D11_MAP type = D3D11_MAP_WRITE_NO_OVERWRITE;
|
||||
|
||||
m_index.start = m_expand_ib_vs_pos;
|
||||
m_expand_ib_vs_pos += count;
|
||||
|
||||
if (m_expand_ib_vs_pos > (INDEX_BUFFER_SIZE / sizeof(u16)))
|
||||
{
|
||||
m_index.start = 0;
|
||||
m_expand_ib_vs_pos = count;
|
||||
type = D3D11_MAP_WRITE_DISCARD;
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE m;
|
||||
if (FAILED(m_ctx->Map(m_expand_ib_vs.get(), 0, type, 0, &m)))
|
||||
return nullptr;
|
||||
|
||||
return static_cast<u16*>(m.pData) + m_index.start;
|
||||
}
|
||||
|
||||
void GSDevice11::VSUnmapIndexBuffer(u32 count)
|
||||
{
|
||||
m_ctx->Unmap(m_expand_ib_vs.get(), 0);
|
||||
m_index.count = count;
|
||||
}
|
||||
|
||||
bool GSDevice11::VSSetIndexBuffer(const void* index, u32 count)
|
||||
{
|
||||
u16* map = VSMapIndexBuffer(count);
|
||||
if (!map)
|
||||
return false;
|
||||
|
||||
std::memcpy(map, index, count * sizeof(u16));
|
||||
VSUnmapIndexBuffer(count);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDevice11::PSSetShaderResource(int i, GSTexture* sr)
|
||||
{
|
||||
// Update local state only, PSUpdateShaderState updates gpu state.
|
||||
m_state.ps_pending_srv[i] = *static_cast<GSTexture11*>(sr);
|
||||
if (sr)
|
||||
m_state.ps_pending_srv[i] = *static_cast<GSTexture11*>(sr);
|
||||
else
|
||||
m_state.ps_pending_srv[i] = nullptr;
|
||||
}
|
||||
|
||||
void GSDevice11::PSSetSamplerState(ID3D11SamplerState* ss0)
|
||||
@@ -2576,14 +2688,10 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector
|
||||
CommitClear(rt);
|
||||
rtv = *static_cast<GSTexture11*>(rt);
|
||||
}
|
||||
if (read_only_dsv)
|
||||
{
|
||||
dsv = read_only_dsv;
|
||||
}
|
||||
else if (ds)
|
||||
if (ds)
|
||||
{
|
||||
CommitClear(ds);
|
||||
dsv = *static_cast<GSTexture11*>(ds);
|
||||
dsv = read_only_dsv ? read_only_dsv : *static_cast<GSTexture11*>(ds);
|
||||
}
|
||||
|
||||
const bool changed = (m_state.rtv != rtv || m_state.dsv != dsv);
|
||||
@@ -2607,6 +2715,9 @@ void GSDevice11::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector
|
||||
m_state.dsv = dsv;
|
||||
m_state.current_ds = ds;
|
||||
}
|
||||
|
||||
PSUnbindConflictingSRVs(m_state.current_rt, read_only_dsv ? nullptr : m_state.current_ds);
|
||||
|
||||
if (changed)
|
||||
m_ctx->OMSetRenderTargets(1, &rtv, dsv);
|
||||
|
||||
@@ -2728,7 +2839,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// Destination Alpha Setup
|
||||
const bool multidraw_fb_copy = m_features.multidraw_fb_copy && (config.require_one_barrier || config.require_full_barrier);
|
||||
const bool need_barrier = config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy);
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
|
||||
{
|
||||
primid_texture = CreateRenderTarget(rtsize.x, rtsize.y, GSTexture::Format::PrimID, false);
|
||||
@@ -2742,7 +2853,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
primid_texture, GSVector4(config.drawarea), m_date.primid_init_ps[static_cast<u8>(config.datm)].get(), nullptr, false);
|
||||
}
|
||||
else if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil ||
|
||||
(config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && !multidraw_fb_copy))
|
||||
(config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && !need_barrier))
|
||||
SetupDATE(colclip_rt ? colclip_rt : config.rt, config.ds, config.datm, config.drawarea);
|
||||
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
@@ -2752,8 +2863,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
Console.Error("D3D11: Failed to upload structured vertices (%u)", config.nverts);
|
||||
return;
|
||||
}
|
||||
|
||||
config.cb_vs.max_depth.y = m_vertex.start;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2764,12 +2873,17 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
}
|
||||
|
||||
if (config.vs.UseExpandIndexBuffer())
|
||||
if (config.vs.UseFixedExpandIndexBuffer())
|
||||
{
|
||||
IASetIndexBuffer(m_expand_ib.get());
|
||||
m_index.start = 0;
|
||||
m_index.count = config.nindices;
|
||||
}
|
||||
else if (config.vs.UseVSExpandIndexBuffer())
|
||||
{
|
||||
VSSetIndexBuffer(config.indices, config.nindices);
|
||||
IASetVertexBuffer(nullptr, 0); // Unbind the vertex buffer to prevent unwanted fetches.
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IASetIndexBuffer(config.indices, config.nindices))
|
||||
@@ -2799,29 +2913,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
config.tex = nullptr;
|
||||
}
|
||||
|
||||
// Preemptively bind srv if possible.
|
||||
// We update the local state, then if there are srv conflicts PSUnbindConflictingSRVs will update the gpu state.
|
||||
if (config.tex)
|
||||
{
|
||||
CommitClear(config.tex);
|
||||
if (m_state.current_rt != config.tex && m_state.current_ds != config.tex)
|
||||
PSSetShaderResource(0, config.tex);
|
||||
}
|
||||
if (config.pal)
|
||||
{
|
||||
CommitClear(config.pal);
|
||||
if (m_state.current_rt != config.pal && m_state.current_ds != config.pal)
|
||||
PSSetShaderResource(1, config.pal);
|
||||
}
|
||||
|
||||
// Should be called before changing current gpu state.
|
||||
PSUnbindConflictingSRVs(colclip_rt ? colclip_rt : config.rt, read_only_dsv ? nullptr : config.ds);
|
||||
|
||||
if (config.tex)
|
||||
PSSetShaderResource(0, config.tex);
|
||||
if (config.pal)
|
||||
PSSetShaderResource(1, config.pal);
|
||||
|
||||
SetupVS(config.vs, &config.cb_vs);
|
||||
SetupPS(config.ps, &config.cb_ps, config.sampler);
|
||||
|
||||
@@ -2833,12 +2924,12 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
GSHWDrawConfig::BlendState(true, CONST_ONE, CONST_ONE, 3 /* MIN */, CONST_ONE, CONST_ZERO, false, 0));
|
||||
SetupOM(dss, blend, 0);
|
||||
OMSetRenderTargets(primid_texture, config.ds, &config.scissor, read_only_dsv);
|
||||
DrawIndexedPrimitive();
|
||||
SetRenderHWShaderResources(config, nullptr);
|
||||
Draw(config);
|
||||
|
||||
config.ps.date = 3;
|
||||
config.alpha_second_pass.ps.date = 3;
|
||||
SetupPS(config.ps, nullptr, config.sampler);
|
||||
PSSetShaderResource(3, primid_texture);
|
||||
}
|
||||
|
||||
// Avoid changing framebuffer just to switch from rt+depth to rt and vice versa.
|
||||
@@ -2846,7 +2937,6 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
GSTexture* draw_ds = config.ds;
|
||||
// Make sure no tex is bound as both rtv and srv at the same time.
|
||||
// All conflicts should've been taken care of by PSUnbindConflictingSRVs.
|
||||
// It is fine to do the optimiation when on slot 0 tex is fb, tex is ds, and slot 2 sw blend as they are copies bound to srv.
|
||||
if (!draw_rt && draw_ds && m_state.rtv && m_state.current_rt && m_state.rtv == *static_cast<GSTexture11*>(m_state.current_rt) &&
|
||||
m_state.current_ds == draw_ds && config.tex != m_state.current_rt && m_state.current_rt->GetSize() == draw_ds->GetSize())
|
||||
{
|
||||
@@ -2858,31 +2948,43 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
draw_ds = m_state.current_ds;
|
||||
}
|
||||
|
||||
if (draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy) || (config.tex && config.tex == config.rt)))
|
||||
const bool tex_is_fb = config.tex && config.tex == draw_rt;
|
||||
const bool rt_feedbackloop_pass1 = config.ps.IsFeedbackLoopRT() || tex_is_fb;
|
||||
const bool rt_feedbackloop_pass2 = config.alpha_second_pass.ps.IsFeedbackLoopRT() || tex_is_fb;
|
||||
if (draw_rt && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
|
||||
(rt_feedbackloop_pass1 || rt_feedbackloop_pass2))))
|
||||
{
|
||||
config.require_one_barrier |= (tex_is_fb && !config.require_full_barrier);
|
||||
config.alpha_second_pass.require_one_barrier |= (tex_is_fb && !config.require_full_barrier);
|
||||
|
||||
// Requires a copy of the RT.
|
||||
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true);
|
||||
|
||||
if (!draw_rt_clone)
|
||||
Console.Warning("D3D11: Failed to allocate temp texture for RT copy.");
|
||||
}
|
||||
|
||||
if (draw_ds && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
|
||||
m_features.depth_feedback == GSDevice::DepthFeedbackSupport::Depth && config.ps.IsFeedbackLoopDepth())
|
||||
const bool ds_feedbackloop_pass1 = config.ps.IsFeedbackLoopDepth();
|
||||
const bool ds_feedbackloop_pass2 = config.alpha_second_pass.ps.IsFeedbackLoopDepth();
|
||||
if (draw_ds && m_features.depth_feedback && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
|
||||
(ds_feedbackloop_pass1 || ds_feedbackloop_pass2))
|
||||
{
|
||||
// Requires a copy of the DS.
|
||||
draw_ds_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_ds->GetFormat(), true);
|
||||
|
||||
if (!draw_ds_clone)
|
||||
Console.Warning("D3D11: Failed to allocate temp texture for DS copy.");
|
||||
}
|
||||
|
||||
OMSetRenderTargets(draw_rt, draw_ds, &config.scissor, read_only_dsv);
|
||||
SetRenderHWShaderResources(config, primid_texture);
|
||||
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend), config.blend.constant);
|
||||
|
||||
// Clear stencil as close as possible to the RT bind, to avoid framebuffer swaps.
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && multidraw_fb_copy)
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && need_barrier)
|
||||
m_ctx->ClearDepthStencilView(*static_cast<GSTexture11*>(draw_ds), D3D11_CLEAR_STENCIL, 0.0f, 1);
|
||||
|
||||
SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds_clone, draw_ds,
|
||||
SendHWDraw(config, rt_feedbackloop_pass1 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass1 ? draw_ds_clone : nullptr, draw_ds,
|
||||
config.require_one_barrier, config.require_full_barrier);
|
||||
|
||||
if (config.blend_multi_pass.enable)
|
||||
@@ -2892,7 +2994,7 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
config.ps.dither = config.blend_multi_pass.dither;
|
||||
SetupPS(config.ps, &config.cb_ps, config.sampler);
|
||||
SetupOM(config.depth, OMBlendSelector(config.colormask, config.blend_multi_pass.blend), config.blend_multi_pass.blend.constant);
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
if (config.alpha_second_pass.enable)
|
||||
@@ -2908,9 +3010,10 @@ void GSDevice11::RenderHW(GSHWDrawConfig& config)
|
||||
SetupPS(config.alpha_second_pass.ps, nullptr, config.sampler);
|
||||
}
|
||||
|
||||
const bool one_barrier = config.alpha_second_pass.require_one_barrier && m_features.multidraw_fb_copy;
|
||||
SetupOM(config.alpha_second_pass.depth, OMBlendSelector(config.alpha_second_pass.colormask, config.blend), config.blend.constant);
|
||||
SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds_clone, draw_ds,
|
||||
false, config.alpha_second_pass.require_full_barrier);
|
||||
SendHWDraw(config, rt_feedbackloop_pass2 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass2 ? draw_ds_clone : nullptr, draw_ds,
|
||||
one_barrier, config.alpha_second_pass.require_full_barrier);
|
||||
}
|
||||
|
||||
if (colclip_rt)
|
||||
@@ -2934,56 +3037,70 @@ void GSDevice11::SendHWDraw(const GSHWDrawConfig& config,
|
||||
GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds,
|
||||
const bool one_barrier, const bool full_barrier)
|
||||
{
|
||||
if (draw_rt_clone || draw_ds_clone)
|
||||
{
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
if ((one_barrier || full_barrier) && !(config.ps.IsFeedbackLoopRT() || config.ps.IsFeedbackLoopDepth())) [[unlikely]]
|
||||
Console.Warning("D3D11: Possible unnecessary copy detected.");
|
||||
if ((one_barrier || full_barrier) && !(config.ps.IsFeedbackLoopRT() || config.ps.IsFeedbackLoopDepth())) [[unlikely]]
|
||||
Console.Warning("D3D11: Possible unnecessary copy detected.");
|
||||
#endif
|
||||
|
||||
auto CopyAndBind = [&](GSVector4i drawarea) {
|
||||
if (draw_rt_clone)
|
||||
{
|
||||
CopyRect(draw_rt, draw_rt_clone, drawarea, drawarea.left, drawarea.top);
|
||||
if ((one_barrier || full_barrier))
|
||||
PSSetShaderResource(2, draw_rt_clone);
|
||||
if (config.tex && config.tex == draw_rt)
|
||||
PSSetShaderResource(0, draw_rt_clone);
|
||||
}
|
||||
if (draw_ds_clone)
|
||||
{
|
||||
CopyRect(draw_ds, draw_ds_clone, drawarea, drawarea.left, drawarea.top);
|
||||
PSSetShaderResource(4, draw_ds_clone);
|
||||
}
|
||||
};
|
||||
|
||||
const GSVector4i rtsize(0, 0, (draw_rt ? draw_rt : draw_ds)->GetWidth(), (draw_rt ? draw_rt : draw_ds)->GetHeight());
|
||||
|
||||
if (full_barrier)
|
||||
auto CopyAndBind = [&](GSVector4i drawarea) {
|
||||
if (draw_rt_clone)
|
||||
{
|
||||
const u32 draw_list_size = static_cast<u32>(config.drawlist->size());
|
||||
const u32 indices_per_prim = config.indices_per_prim;
|
||||
CopyRect(draw_rt, draw_rt_clone, drawarea, drawarea.left, drawarea.top);
|
||||
if ((one_barrier || full_barrier))
|
||||
PSSetShaderResource(2, draw_rt_clone);
|
||||
if (config.tex && config.tex == draw_rt)
|
||||
PSSetShaderResource(0, draw_rt_clone);
|
||||
}
|
||||
if (draw_ds_clone)
|
||||
{
|
||||
CopyRect(draw_ds, draw_ds_clone, drawarea, drawarea.left, drawarea.top);
|
||||
PSSetShaderResource(4, draw_ds_clone);
|
||||
}
|
||||
};
|
||||
|
||||
pxAssert(config.drawlist && !config.drawlist->empty());
|
||||
pxAssert(config.drawlist_bbox && static_cast<u32>(config.drawlist_bbox->size()) == draw_list_size);
|
||||
const GSVector4i rtsize(0, 0, (draw_rt ? draw_rt : draw_ds)->GetWidth(), (draw_rt ? draw_rt : draw_ds)->GetHeight());
|
||||
|
||||
for (u32 n = 0, p = 0; n < draw_list_size; n++)
|
||||
{
|
||||
const u32 count = (*config.drawlist)[n] * indices_per_prim;
|
||||
if (full_barrier)
|
||||
{
|
||||
const u32 draw_list_size = static_cast<u32>(config.drawlist->size());
|
||||
const u32 indices_per_prim = config.indices_per_prim;
|
||||
|
||||
const GSVector4i original_bbox = (*config.drawlist_bbox)[n].rintersect(config.drawarea);
|
||||
CopyAndBind(ProcessCopyArea(rtsize, original_bbox));
|
||||
pxAssert(config.drawlist && !config.drawlist->empty());
|
||||
pxAssert(config.drawlist_bbox && static_cast<u32>(config.drawlist_bbox->size()) == draw_list_size);
|
||||
|
||||
DrawIndexedPrimitive(p, count);
|
||||
p += count;
|
||||
}
|
||||
for (u32 n = 0, p = 0; n < draw_list_size; n++)
|
||||
{
|
||||
const u32 count = (*config.drawlist)[n] * indices_per_prim;
|
||||
|
||||
return;
|
||||
const GSVector4i original_bbox = (*config.drawlist_bbox)[n].rintersect(config.drawarea);
|
||||
CopyAndBind(ProcessCopyArea(rtsize, original_bbox));
|
||||
|
||||
Draw(config, p, count);
|
||||
|
||||
p += count;
|
||||
}
|
||||
|
||||
// Optimization: For alpha second pass we can reuse the copy snapshot from the first pass.
|
||||
CopyAndBind(ProcessCopyArea(rtsize, config.drawarea));
|
||||
return;
|
||||
}
|
||||
|
||||
DrawIndexedPrimitive();
|
||||
if (one_barrier)
|
||||
CopyAndBind(ProcessCopyArea(rtsize, config.drawarea));
|
||||
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
void GSDevice11::SetRenderHWShaderResources(const GSHWDrawConfig& config, GSTexture* primid_texture)
|
||||
{
|
||||
if (config.tex && config.tex != config.rt)
|
||||
{
|
||||
CommitClear(config.tex);
|
||||
PSSetShaderResource(0, config.tex);
|
||||
}
|
||||
if (config.pal)
|
||||
{
|
||||
CommitClear(config.pal);
|
||||
PSSetShaderResource(1, config.pal);
|
||||
}
|
||||
if (primid_texture)
|
||||
PSSetShaderResource(3, primid_texture);
|
||||
}
|
||||
|
||||
@@ -125,12 +125,15 @@ private:
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_ib;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_expand_vb;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_expand_ib;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_expand_ib_vs;
|
||||
wil::com_ptr_nothrow<ID3D11ShaderResourceView> m_expand_vb_srv;
|
||||
wil::com_ptr_nothrow<ID3D11ShaderResourceView> m_expand_ib_vs_srv;
|
||||
|
||||
D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_10_0;
|
||||
u32 m_vb_pos = 0; // bytes
|
||||
u32 m_ib_pos = 0; // indices/sizeof(u32)
|
||||
u32 m_ib_pos = 0; // indices/sizeof(u16)
|
||||
u32 m_structured_vb_pos = 0; // bytes
|
||||
u32 m_expand_ib_vs_pos = 0; // indices/sizeof(u16)
|
||||
|
||||
bool m_allow_tearing_supported = false;
|
||||
bool m_using_flip_model_swap_chain = true;
|
||||
@@ -146,6 +149,7 @@ private:
|
||||
ID3D11Buffer* index_buffer;
|
||||
ID3D11VertexShader* vs;
|
||||
ID3D11Buffer* vs_cb;
|
||||
ID3D11Buffer* vs_pc;
|
||||
std::array<ID3D11ShaderResourceView*, MAX_TEXTURES> ps_pending_srv;
|
||||
std::array<ID3D11ShaderResourceView*, MAX_TEXTURES> ps_current_srv;
|
||||
ID3D11PixelShader* ps;
|
||||
@@ -154,6 +158,7 @@ private:
|
||||
std::array<ID3D11SamplerState*, MAX_SAMPLERS> ps_current_ss;
|
||||
GSVector2i viewport;
|
||||
GSVector4i scissor;
|
||||
ID3D11Buffer* vb;
|
||||
u32 vb_stride;
|
||||
ID3D11DepthStencilState* dss;
|
||||
u8 sref;
|
||||
@@ -246,6 +251,7 @@ private:
|
||||
|
||||
std::unordered_map<u32, GSVertexShader11> m_vs;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_vs_cb;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_vs_pc;
|
||||
std::unordered_map<u32, wil::com_ptr_nothrow<ID3D11GeometryShader>> m_gs;
|
||||
std::unordered_map<PSSelector, wil::com_ptr_nothrow<ID3D11PixelShader>, GSHWDrawConfig::PSSelectorHash> m_ps;
|
||||
wil::com_ptr_nothrow<ID3D11Buffer> m_ps_cb;
|
||||
@@ -256,6 +262,7 @@ private:
|
||||
|
||||
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
|
||||
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
|
||||
GSHWDrawConfig::VSPushConstants m_vs_pc_cache;
|
||||
|
||||
D3D11ShaderCache m_shader_cache;
|
||||
std::string m_tfx_source;
|
||||
@@ -294,9 +301,15 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
// Helpers and utility draws.
|
||||
void DrawPrimitive();
|
||||
void DrawIndexedPrimitive();
|
||||
void DrawIndexedPrimitive(int offset, int count);
|
||||
void DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion);
|
||||
|
||||
// Main GS primitive draws.
|
||||
void Draw(const GSHWDrawConfig& config);
|
||||
void Draw(const GSHWDrawConfig& config, int offset, int count);
|
||||
|
||||
void PushDebugGroup(const char* fmt, ...) override;
|
||||
void PopDebugGroup() override;
|
||||
@@ -323,6 +336,7 @@ public:
|
||||
void* IAMapVertexBuffer(u32 stride, u32 count);
|
||||
void IAUnmapVertexBuffer(u32 stride, u32 count);
|
||||
bool IASetVertexBuffer(const void* vertex, u32 stride, u32 count);
|
||||
void IASetVertexBuffer(ID3D11Buffer* buffer, u32 stride);
|
||||
bool IASetExpandVertexBuffer(const void* vertex, u32 stride, u32 count);
|
||||
|
||||
u16* IAMapIndexBuffer(u32 count);
|
||||
@@ -334,6 +348,10 @@ public:
|
||||
void IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY topology);
|
||||
|
||||
void VSSetShader(ID3D11VertexShader* vs, ID3D11Buffer* vs_cb);
|
||||
void VSSetPushConstants(u32 base_vertex, u32 base_index = 0, bool force_update = false);
|
||||
u16* VSMapIndexBuffer(u32 count);
|
||||
void VSUnmapIndexBuffer(u32 count);
|
||||
bool VSSetIndexBuffer(const void* indices, u32 count);
|
||||
|
||||
void PSSetShaderResource(int i, GSTexture* sr);
|
||||
void PSSetShader(ID3D11PixelShader* ps, ID3D11Buffer* ps_cb);
|
||||
@@ -357,6 +375,7 @@ public:
|
||||
void SendHWDraw(const GSHWDrawConfig& config,
|
||||
GSTexture* draw_rt_clone, GSTexture* draw_rt, GSTexture* draw_ds_clone, GSTexture* draw_ds,
|
||||
const bool one_barrier, const bool full_barrier);
|
||||
void SetRenderHWShaderResources(const GSHWDrawConfig& config, GSTexture* primid_texture);
|
||||
|
||||
void ClearSamplerCache() override;
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#include "GS/Renderers/DX12/D3D12ShaderCache.h"
|
||||
#include "GS/Renderers/DX11/D3D.h"
|
||||
#include "GS/GS.h"
|
||||
|
||||
#include "Config.h"
|
||||
@@ -54,16 +53,18 @@ bool D3D12ShaderCache::CacheIndexKey::operator!=(const CacheIndexKey& key) const
|
||||
source_length != key.source_length);
|
||||
}
|
||||
|
||||
bool D3D12ShaderCache::Open(D3D_FEATURE_LEVEL feature_level, bool debug)
|
||||
bool D3D12ShaderCache::Open(D3D::ShaderModel shader_model, bool debug)
|
||||
{
|
||||
m_feature_level = feature_level;
|
||||
// Only support SM5.1 for now, which is the minimum for D3D12.
|
||||
pxAssert(shader_model >= D3D::ShaderModel::SM51);
|
||||
m_shader_model = shader_model;
|
||||
m_debug = debug;
|
||||
|
||||
bool result = true;
|
||||
|
||||
if (!GSConfig.DisableShaderCache)
|
||||
{
|
||||
const std::string base_shader_filename = GetCacheBaseFileName("shaders", feature_level, debug);
|
||||
const std::string base_shader_filename = GetCacheBaseFileName("shaders", m_shader_model, debug);
|
||||
const std::string shader_index_filename = base_shader_filename + ".idx";
|
||||
const std::string shader_blob_filename = base_shader_filename + ".bin";
|
||||
|
||||
@@ -75,7 +76,7 @@ bool D3D12ShaderCache::Open(D3D_FEATURE_LEVEL feature_level, bool debug)
|
||||
|
||||
if (result)
|
||||
{
|
||||
const std::string base_pipelines_filename = GetCacheBaseFileName("pipelines", feature_level, debug);
|
||||
const std::string base_pipelines_filename = GetCacheBaseFileName("pipelines", m_shader_model, debug);
|
||||
const std::string pipelines_index_filename = base_pipelines_filename + ".idx";
|
||||
const std::string pipelines_blob_filename = base_pipelines_filename + ".bin";
|
||||
|
||||
@@ -133,7 +134,7 @@ void D3D12ShaderCache::InvalidatePipelineCache()
|
||||
if (GSConfig.DisableShaderCache)
|
||||
return;
|
||||
|
||||
const std::string base_pipelines_filename = GetCacheBaseFileName("pipelines", m_feature_level, m_debug);
|
||||
const std::string base_pipelines_filename = GetCacheBaseFileName("pipelines", m_shader_model, m_debug);
|
||||
const std::string pipelines_index_filename = base_pipelines_filename + ".idx";
|
||||
const std::string pipelines_blob_filename = base_pipelines_filename + ".bin";
|
||||
CreateNew(pipelines_index_filename, pipelines_blob_filename, m_pipeline_index_file, m_pipeline_blob_file);
|
||||
@@ -253,21 +254,12 @@ bool D3D12ShaderCache::ReadExisting(const std::string& index_filename, const std
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string D3D12ShaderCache::GetCacheBaseFileName(const std::string_view type, D3D_FEATURE_LEVEL feature_level, bool debug)
|
||||
std::string D3D12ShaderCache::GetCacheBaseFileName(const std::string_view type, D3D::ShaderModel shader_model, bool debug)
|
||||
{
|
||||
std::string base_filename = "d3d12_";
|
||||
base_filename += type;
|
||||
base_filename += "_";
|
||||
|
||||
switch (feature_level)
|
||||
{
|
||||
case D3D_FEATURE_LEVEL_11_0:
|
||||
base_filename += "sm50";
|
||||
break;
|
||||
default:
|
||||
base_filename += "unk";
|
||||
break;
|
||||
}
|
||||
base_filename += D3D::ShaderModelToCacheString(shader_model);
|
||||
|
||||
if (debug)
|
||||
base_filename += "_debug";
|
||||
@@ -497,15 +489,15 @@ D3D12ShaderCache::ComPtr<ID3DBlob> D3D12ShaderCache::CompileAndAddShaderBlob(
|
||||
{
|
||||
case EntryType::VertexShader:
|
||||
blob =
|
||||
D3D::CompileShader(D3D::ShaderType::Vertex, m_feature_level, m_debug, shader_code, macros, entry_point);
|
||||
D3D::CompileShader(D3D::ShaderType::Vertex, m_shader_model, m_debug, shader_code, macros, entry_point);
|
||||
break;
|
||||
case EntryType::PixelShader:
|
||||
blob =
|
||||
D3D::CompileShader(D3D::ShaderType::Pixel, m_feature_level, m_debug, shader_code, macros, entry_point);
|
||||
D3D::CompileShader(D3D::ShaderType::Pixel, m_shader_model, m_debug, shader_code, macros, entry_point);
|
||||
break;
|
||||
case EntryType::ComputeShader:
|
||||
blob = D3D::CompileShader(
|
||||
D3D::ShaderType::Compute, m_feature_level, m_debug, shader_code, macros, entry_point);
|
||||
blob =
|
||||
D3D::CompileShader(D3D::ShaderType::Compute, m_shader_model, m_debug, shader_code, macros, entry_point);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
#pragma once
|
||||
#include "GS/Renderers/DX11/D3D.h"
|
||||
|
||||
#include "common/Pcsx2Defs.h"
|
||||
#include "common/HashCombine.h"
|
||||
@@ -32,10 +33,9 @@ public:
|
||||
D3D12ShaderCache();
|
||||
~D3D12ShaderCache();
|
||||
|
||||
__fi D3D_FEATURE_LEVEL GetFeatureLevel() const { return m_feature_level; }
|
||||
__fi bool UsingDebugShaders() const { return m_debug; }
|
||||
|
||||
bool Open(D3D_FEATURE_LEVEL feature_level, bool debug);
|
||||
bool Open(D3D::ShaderModel shader_model, bool debug);
|
||||
void Close();
|
||||
|
||||
__fi ComPtr<ID3DBlob> GetVertexShader(
|
||||
@@ -95,7 +95,7 @@ private:
|
||||
|
||||
using CacheIndex = std::unordered_map<CacheIndexKey, CacheIndexData, CacheIndexEntryHasher>;
|
||||
|
||||
static std::string GetCacheBaseFileName(const std::string_view type, D3D_FEATURE_LEVEL feature_level, bool debug);
|
||||
static std::string GetCacheBaseFileName(const std::string_view type, D3D::ShaderModel shader_model, bool debug);
|
||||
static CacheIndexKey GetShaderCacheKey(
|
||||
EntryType type, const std::string_view shader_code, const D3D_SHADER_MACRO* macros, const char* entry_point);
|
||||
static CacheIndexKey GetPipelineCacheKey(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& gpdesc);
|
||||
@@ -123,6 +123,6 @@ private:
|
||||
std::FILE* m_pipeline_blob_file = nullptr;
|
||||
CacheIndex m_pipeline_index;
|
||||
|
||||
D3D_FEATURE_LEVEL m_feature_level = D3D_FEATURE_LEVEL_11_0;
|
||||
D3D::ShaderModel m_shader_model = D3D::ShaderModel::SM51;
|
||||
bool m_debug = false;
|
||||
};
|
||||
|
||||
@@ -282,12 +282,19 @@ bool GSDevice12::CreateDevice(u32& vendor_id)
|
||||
}
|
||||
}
|
||||
|
||||
// Intel Haswell doesn't actually support DX12 even tho the device is created which results in a crash,
|
||||
// to get around this check if device can be created using feature level 12 (skylake+).
|
||||
const bool isIntel = (vendor_id == 0x163C || vendor_id == 0x8086 || vendor_id == 0x8087);
|
||||
|
||||
// Create the actual device.
|
||||
// Intel Haswell DX12 support is specific:
|
||||
// Newerest drivers have dx12 support disabled so the last driver to support dx12 is 15.40.42.5063.
|
||||
// Shader cache must be also disabled, and make sure Debug Device option is disabled as well.
|
||||
// Let's enable it on dev/debug for testing purposes so we don't have to change this all the time.
|
||||
// TODO: Find out the status of Broadwell.
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
hr = D3D12CreateDevice(m_adapter.get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||
#else
|
||||
const bool isIntel = (vendor_id == 0x163C || vendor_id == 0x8086 || vendor_id == 0x8087);
|
||||
hr = D3D12CreateDevice(m_adapter.get(), isIntel ? D3D_FEATURE_LEVEL_12_0 : D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_device));
|
||||
#endif
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Console.Error("D3D12: Failed to create device: %08X", hr);
|
||||
@@ -549,6 +556,7 @@ bool GSDevice12::ExecuteCommandList(WaitType wait_for_completion)
|
||||
// Flush stream buffers to GPU memory
|
||||
m_vertex_stream_buffer.FlushMemory();
|
||||
m_index_stream_buffer.FlushMemory();
|
||||
m_expand_index_stream_buffer.FlushMemory();
|
||||
m_vertex_constant_buffer.FlushMemory();
|
||||
m_pixel_constant_buffer.FlushMemory();
|
||||
|
||||
@@ -603,6 +611,9 @@ bool GSDevice12::ExecuteCommandList(WaitType wait_for_completion)
|
||||
if (wait_for_completion != WaitType::None)
|
||||
WaitForFence(res.ready_fence_value, wait_for_completion == WaitType::Spin);
|
||||
|
||||
// Push constants need to be refreshed each command list.
|
||||
m_dirty_flags |= DIRTY_FLAG_VS_PUSH_CONSTANTS;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -881,7 +892,7 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
m_tfx_source = std::move(*shader);
|
||||
}
|
||||
|
||||
if (!m_shader_cache.Open(m_feature_level, GSConfig.UseDebugDevice))
|
||||
if (!m_shader_cache.Open(D3D::ShaderModel::SM51, GSConfig.UseDebugDevice))
|
||||
Console.Warning("D3D12: Shader cache failed to open.");
|
||||
|
||||
if (!CreateRootSignatures())
|
||||
@@ -1401,15 +1412,8 @@ bool GSDevice12::CheckFeatures(const u32& vendor_id)
|
||||
m_features.cas_sharpening = true;
|
||||
m_features.test_and_sample_depth = true;
|
||||
m_features.vs_expand = !GSConfig.DisableVertexShaderExpand;
|
||||
if (m_features.texture_barrier && (GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto ||
|
||||
GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::DepthAsRT))
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::DepthAsRT;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
}
|
||||
m_features.depth_feedback = false;
|
||||
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
|
||||
|
||||
m_features.dxt_textures = SupportsTextureFormat(DXGI_FORMAT_BC1_UNORM) &&
|
||||
SupportsTextureFormat(DXGI_FORMAT_BC2_UNORM) &&
|
||||
@@ -1460,8 +1464,7 @@ void GSDevice12::DrawPrimitive()
|
||||
|
||||
void GSDevice12::DrawIndexedPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
GetCommandList().list4->DrawIndexedInstanced(m_index.count, 1, m_index.start, m_vertex.start, 0);
|
||||
DrawIndexedPrimitive(0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDevice12::DrawIndexedPrimitive(int offset, int count)
|
||||
@@ -1471,6 +1474,40 @@ void GSDevice12::DrawIndexedPrimitive(int offset, int count)
|
||||
GetCommandList().list4->DrawIndexedInstanced(count, 1, m_index.start + offset, m_vertex.start, 0);
|
||||
}
|
||||
|
||||
void GSDevice12::DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion)
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
if (vs_indexing)
|
||||
{
|
||||
SetVSPushConstants(m_vertex.start, m_index.start + offset);
|
||||
GetCommandList().list4->DrawInstanced(count * vs_indexing_expansion, 1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVSPushConstants(m_vertex.start);
|
||||
GetCommandList().list4->DrawIndexedInstanced(count, 1, m_index.start + offset, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice12::Draw(const GSHWDrawConfig& config, int offset, int count)
|
||||
{
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
{
|
||||
const bool vs_indexing = config.vs.UseVSExpandIndexBuffer();
|
||||
const u32 vs_indexing_expansion = GetExpansionFactor(config.vs.expand);
|
||||
DrawIndexedPrimitiveVSExpand(offset, count, vs_indexing, vs_indexing_expansion);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawIndexedPrimitive(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice12::Draw(const GSHWDrawConfig& config)
|
||||
{
|
||||
Draw(config, 0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDevice12::LookupNativeFormat(GSTexture::Format format, DXGI_FORMAT* d3d_format, DXGI_FORMAT* srv_format,
|
||||
DXGI_FORMAT* rtv_format, DXGI_FORMAT* dsv_format) const
|
||||
{
|
||||
@@ -2383,22 +2420,32 @@ void GSDevice12::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
|
||||
void GSDevice12::UploadIndices(D3D12StreamBuffer& buffer, const void* index, size_t count)
|
||||
{
|
||||
const u32 size = sizeof(u16) * static_cast<u32>(count);
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u16)))
|
||||
if (!buffer.ReserveMemory(size, sizeof(u16)))
|
||||
{
|
||||
ExecuteCommandListAndRestartRenderPass(false, "Uploading bytes to index buffer");
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u16)))
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
if (!buffer.ReserveMemory(size, sizeof(u16)))
|
||||
pxFailRel("Failed to reserve space for indices");
|
||||
}
|
||||
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.start = buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.count = count;
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT);
|
||||
|
||||
std::memcpy(m_index_stream_buffer.GetCurrentHostPointer(), index, size);
|
||||
m_index_stream_buffer.CommitMemory(size);
|
||||
std::memcpy(buffer.GetCurrentHostPointer(), index, size);
|
||||
buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
UploadIndices(m_index_stream_buffer, index, count);
|
||||
SetIndexBuffer(m_index_stream_buffer.GetGPUPointer(), m_index_stream_buffer.GetSize(), DXGI_FORMAT_R16_UINT);
|
||||
}
|
||||
|
||||
void GSDevice12::VSSetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
UploadIndices(m_expand_index_stream_buffer, index, count);
|
||||
}
|
||||
|
||||
void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds_as_rt, GSTexture* ds, const GSVector4i& scissor,
|
||||
@@ -2463,37 +2510,33 @@ bool GSDevice12::GetSampler(D3D12DescriptorHandle* cpu_handle, GSHWDrawConfig::S
|
||||
return true;
|
||||
}
|
||||
|
||||
D3D12_SAMPLER_DESC sd = {};
|
||||
const int anisotropy = GSConfig.MaxAnisotropy;
|
||||
if (anisotropy > 1 && ss.aniso)
|
||||
{
|
||||
sd.Filter = D3D12_FILTER_ANISOTROPIC;
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::array<D3D12_FILTER, 8> filters = {{
|
||||
D3D12_FILTER_MIN_MAG_MIP_POINT, // 000 / min=point,mag=point,mip=point
|
||||
D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT, // 001 / min=linear,mag=point,mip=point
|
||||
D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, // 010 / min=point,mag=linear,mip=point
|
||||
D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT, // 011 / min=linear,mag=linear,mip=point
|
||||
D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR, // 100 / min=point,mag=point,mip=linear
|
||||
D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, // 101 / min=linear,mag=point,mip=linear
|
||||
D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR, // 110 / min=point,mag=linear,mip=linear
|
||||
D3D12_FILTER_MIN_MAG_MIP_LINEAR, // 111 / min=linear,mag=linear,mip=linear
|
||||
}};
|
||||
static constexpr std::array<D3D12_FILTER, 8> filters = {{
|
||||
D3D12_FILTER_MIN_MAG_MIP_POINT, // 000 / min=point,mag=point,mip=point
|
||||
D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT, // 001 / min=linear,mag=point,mip=point
|
||||
D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, // 010 / min=point,mag=linear,mip=point
|
||||
D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT, // 011 / min=linear,mag=linear,mip=point
|
||||
D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR, // 100 / min=point,mag=point,mip=linear
|
||||
D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, // 101 / min=linear,mag=point,mip=linear
|
||||
D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR, // 110 / min=point,mag=linear,mip=linear
|
||||
D3D12_FILTER_MIN_MAG_MIP_LINEAR, // 111 / min=linear,mag=linear,mip=linear
|
||||
}};
|
||||
|
||||
const u8 index = (static_cast<u8>(ss.IsMipFilterLinear()) << 2) |
|
||||
(static_cast<u8>(ss.IsMagFilterLinear()) << 1) | static_cast<u8>(ss.IsMinFilterLinear());
|
||||
sd.Filter = filters[index];
|
||||
}
|
||||
const u8 index = (static_cast<u8>(ss.IsMipFilterLinear()) << 2) |
|
||||
(static_cast<u8>(ss.IsMagFilterLinear()) << 1) |
|
||||
static_cast<u8>(ss.IsMinFilterLinear());
|
||||
|
||||
sd.AddressU = ss.tau ? D3D12_TEXTURE_ADDRESS_MODE_WRAP : D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
sd.AddressV = ss.tav ? D3D12_TEXTURE_ADDRESS_MODE_WRAP : D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
sd.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
|
||||
sd.MinLOD = 0.0f;
|
||||
sd.MaxLOD = (ss.lodclamp || !ss.UseMipmapFiltering()) ? 0.25f : FLT_MAX;
|
||||
sd.MaxAnisotropy = std::clamp<u8>(GSConfig.MaxAnisotropy, 1, 16);
|
||||
sd.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
|
||||
const D3D12_SAMPLER_DESC sd = {
|
||||
filters[index], // Filter
|
||||
ss.tau ? D3D12_TEXTURE_ADDRESS_MODE_WRAP : D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // Address u
|
||||
ss.tav ? D3D12_TEXTURE_ADDRESS_MODE_WRAP : D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // Address v
|
||||
D3D12_TEXTURE_ADDRESS_MODE_CLAMP, // Address w
|
||||
0.0f, // Lod bias
|
||||
1, // Anisotropy
|
||||
D3D12_COMPARISON_FUNC_NEVER, // comparison function
|
||||
{}, // Border colour
|
||||
0.0f, // Min lod
|
||||
(ss.lodclamp || !ss.UseMipmapFiltering()) ? 0.25f : FLT_MAX, // Max lod
|
||||
};
|
||||
|
||||
if (!GetSamplerHeapManager().Allocate(cpu_handle))
|
||||
return false;
|
||||
@@ -2591,6 +2634,12 @@ bool GSDevice12::CreateBuffers()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_expand_index_stream_buffer.Create(m_features.aa1 ? INDEX_BUFFER_SIZE : 4, false))
|
||||
{
|
||||
Host::ReportErrorAsync("GS", "Failed to allocate expansion index buffer (VS resource)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_vertex_constant_buffer.Create(VERTEX_UNIFORM_BUFFER_SIZE, !m_uma))
|
||||
{
|
||||
Host::ReportErrorAsync("GS", "Failed to allocate vertex uniform buffer");
|
||||
@@ -2641,9 +2690,11 @@ bool GSDevice12::CreateRootSignatures()
|
||||
rsb.AddCBVParameter(0, D3D12_SHADER_VISIBILITY_ALL);
|
||||
rsb.AddCBVParameter(1, D3D12_SHADER_VISIBILITY_PIXEL);
|
||||
rsb.AddSRVParameter(0, D3D12_SHADER_VISIBILITY_VERTEX);
|
||||
rsb.AddSRVParameter(5, D3D12_SHADER_VISIBILITY_VERTEX);
|
||||
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, 2, D3D12_SHADER_VISIBILITY_PIXEL); // Source / Palette
|
||||
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, NUM_TFX_SAMPLERS, D3D12_SHADER_VISIBILITY_PIXEL);
|
||||
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 3, D3D12_SHADER_VISIBILITY_PIXEL); // RT / PrimID / Depth
|
||||
rsb.Add32BitConstants(2, sizeof(m_vs_pc_cache) / sizeof(u32), D3D12_SHADER_VISIBILITY_VERTEX);
|
||||
if (!(m_tfx_root_signature = rsb.Create()))
|
||||
return false;
|
||||
D3D12::SetObjectName(m_tfx_root_signature.get(), "TFX root signature");
|
||||
@@ -3037,6 +3088,7 @@ void GSDevice12::DestroyResources()
|
||||
m_vertex_constant_buffer.Destroy(false);
|
||||
m_index_stream_buffer.Destroy(false);
|
||||
m_vertex_stream_buffer.Destroy(false);
|
||||
m_expand_index_stream_buffer.Destroy(false);
|
||||
|
||||
m_utility_root_signature.reset();
|
||||
m_tfx_root_signature.reset();
|
||||
@@ -3157,6 +3209,9 @@ const ID3DBlob* GSDevice12::GetTFXPixelShader(const GSHWDrawConfig::PSSelector&
|
||||
sm.AddMacro("PS_NO_COLOR", sel.no_color);
|
||||
sm.AddMacro("PS_NO_COLOR1", sel.no_color1);
|
||||
sm.AddMacro("PS_ZTST", sel.ztst);
|
||||
sm.AddMacro("PS_AA1", static_cast<u32>(sel.aa1));
|
||||
sm.AddMacro("PS_ABE", sel.abe);
|
||||
sm.AddMacro("PS_ANISOTROPIC_FILTERING", sel.sw_aniso);
|
||||
|
||||
ComPtr<ID3DBlob> ps(m_shader_cache.GetPixelShader(m_tfx_source, sm.GetPtr(), "ps_main"));
|
||||
it = m_tfx_pixel_shaders.emplace(sel, std::move(ps)).first;
|
||||
@@ -3363,7 +3418,7 @@ void GSDevice12::ExecuteCommandListAndRestartRenderPass(bool wait_for_completion
|
||||
{
|
||||
Console.Warning("D3D12: Executing command buffer due to '%s'", reason);
|
||||
|
||||
const bool was_in_render_pass = m_in_render_pass;
|
||||
const bool was_in_render_pass = InRenderPass();
|
||||
EndRenderPass();
|
||||
ExecuteCommandList(GetWaitType(wait_for_completion, GSConfig.HWSpinCPUForReadbacks));
|
||||
InvalidateCachedState();
|
||||
@@ -3393,7 +3448,7 @@ void GSDevice12::ExecuteCommandListForReadback()
|
||||
|
||||
void GSDevice12::InvalidateCachedState()
|
||||
{
|
||||
m_dirty_flags |= DIRTY_BASE_STATE | DIRTY_TFX_STATE | DIRTY_UTILITY_STATE | DIRTY_CONSTANT_BUFFER_STATE;
|
||||
m_dirty_flags |= DIRTY_BASE_STATE | DIRTY_ROOT_PARAMS | DIRTY_TFX_STATE | DIRTY_UTILITY_STATE | DIRTY_CONSTANT_BUFFER_STATE;
|
||||
m_current_root_signature = RootSignature::Undefined;
|
||||
m_utility_texture_cpu.Clear();
|
||||
m_utility_texture_gpu.Clear();
|
||||
@@ -3786,7 +3841,7 @@ __ri void GSDevice12::ApplyBaseState(u32 flags, ID3D12GraphicsCommandList* cmdli
|
||||
if (flags & DIRTY_FLAG_PRIMITIVE_TOPOLOGY)
|
||||
cmdlist->IASetPrimitiveTopology(m_primitive_topology);
|
||||
|
||||
if (flags & DIRTY_FLAG_PIPELINE)
|
||||
if ((flags & DIRTY_FLAG_PIPELINE) && m_current_pipeline)
|
||||
cmdlist->SetPipelineState(const_cast<ID3D12PipelineState*>(m_current_pipeline));
|
||||
|
||||
if (flags & DIRTY_FLAG_VIEWPORT)
|
||||
@@ -3918,9 +3973,7 @@ bool GSDevice12::ApplyTFXState(bool already_execed)
|
||||
if (m_current_root_signature != RootSignature::TFX)
|
||||
{
|
||||
m_current_root_signature = RootSignature::TFX;
|
||||
flags |= DIRTY_FLAG_VS_CONSTANT_BUFFER_BINDING | DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING |
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE | DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE |
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 | DIRTY_FLAG_PIPELINE;
|
||||
flags |= DIRTY_ROOT_PARAMS | DIRTY_FLAG_PIPELINE;
|
||||
cmdlist->SetGraphicsRootSignature(m_tfx_root_signature.get());
|
||||
}
|
||||
|
||||
@@ -3928,10 +3981,17 @@ bool GSDevice12::ApplyTFXState(bool already_execed)
|
||||
cmdlist->SetGraphicsRootConstantBufferView(TFX_ROOT_SIGNATURE_PARAM_VS_CBV, m_tfx_constant_buffers[0]);
|
||||
if (flags & DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING)
|
||||
cmdlist->SetGraphicsRootConstantBufferView(TFX_ROOT_SIGNATURE_PARAM_PS_CBV, m_tfx_constant_buffers[1]);
|
||||
if (m_features.vs_expand && (flags & DIRTY_FLAG_VS_PUSH_CONSTANTS))
|
||||
SetVSPushConstants(m_vs_pc_cache.base_vertex, m_vs_pc_cache.base_index, true);
|
||||
if (flags & DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING)
|
||||
{
|
||||
cmdlist->SetGraphicsRootShaderResourceView(TFX_ROOT_SIGNATURE_PARAM_VS_SRV,
|
||||
m_vertex_stream_buffer.GetGPUPointer() + m_vertex.start * sizeof(GSVertex));
|
||||
cmdlist->SetGraphicsRootShaderResourceView(TFX_ROOT_SIGNATURE_PARAM_VS_VB_SRV,
|
||||
m_vertex_stream_buffer.GetGPUPointer());
|
||||
}
|
||||
if (flags & DIRTY_FLAG_VS_INDEX_BUFFER_BINDING)
|
||||
{
|
||||
cmdlist->SetGraphicsRootShaderResourceView(TFX_ROOT_SIGNATURE_PARAM_VS_IB_SRV,
|
||||
m_expand_index_stream_buffer.GetGPUPointer());
|
||||
}
|
||||
if (flags & DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE)
|
||||
cmdlist->SetGraphicsRootDescriptorTable(TFX_ROOT_SIGNATURE_PARAM_PS_TEXTURES, m_tfx_textures_handle_gpu);
|
||||
@@ -3982,6 +4042,19 @@ void GSDevice12::SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb)
|
||||
m_dirty_flags |= DIRTY_FLAG_PS_CONSTANT_BUFFER;
|
||||
}
|
||||
|
||||
void GSDevice12::SetVSPushConstants(u32 base_vertex, u32 base_index, bool force_update)
|
||||
{
|
||||
GSHWDrawConfig::VSPushConstants pc;
|
||||
pc.base_vertex = base_vertex;
|
||||
pc.base_index = base_index;
|
||||
if (m_vs_pc_cache.Update(pc) || force_update)
|
||||
{
|
||||
GetCommandList().list4->SetGraphicsRoot32BitConstants(
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_PUSH_CONSTANTS, sizeof(m_vs_pc_cache) / sizeof(u32),
|
||||
&m_vs_pc_cache, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDevice12::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox)
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
@@ -4078,7 +4151,7 @@ GSTexture12* GSDevice12::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config, Pipe
|
||||
init_pipe.ps.no_color = false;
|
||||
init_pipe.ps.no_color1 = true;
|
||||
if (BindDrawPipeline(init_pipe))
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
|
||||
// image is initialized/prepass is done, so finish up and get ready to do the "real" draw
|
||||
EndRenderPass();
|
||||
@@ -4177,7 +4250,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// Destination Alpha Setup
|
||||
const bool need_barrier = m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier);
|
||||
const bool need_barrier = config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier);
|
||||
switch (config.destination_alpha)
|
||||
{
|
||||
case GSHWDrawConfig::DestinationAlphaMode::Off: // No setup
|
||||
@@ -4278,7 +4351,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
(draw_ds && static_cast<GSTexture12*>(draw_ds)->GetSRVDescriptor() == m_tfx_textures[0])))
|
||||
PSSetShaderResource(0, nullptr, false);
|
||||
|
||||
if (m_in_render_pass && (m_current_render_target == draw_rt || m_current_depth_target == draw_ds))
|
||||
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
|
||||
@@ -4288,17 +4361,18 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
draw_rt = m_current_render_target;
|
||||
m_pipeline_selector.rt = true;
|
||||
}
|
||||
}
|
||||
else if (!draw_ds && 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;
|
||||
else if (!draw_ds && 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*>(config.ds_as_rt);
|
||||
GSTexture12* draw_ds_as_rt = static_cast<GSTexture12*>(m_ds_as_rt);
|
||||
|
||||
const bool feedback_rt = draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier) || (config.tex && config.tex == config.rt));
|
||||
const bool feedback_rt = draw_rt && (((config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier)) && (config.ps.IsFeedbackLoopRT() ||
|
||||
config.alpha_second_pass.ps.IsFeedbackLoopRT())) || (config.tex && config.tex == config.rt));
|
||||
const bool feedback_depth = draw_ds_as_rt != nullptr;
|
||||
|
||||
if (feedback_rt && !m_features.texture_barrier)
|
||||
@@ -4338,7 +4412,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// Begin render pass if new target or out of the area.
|
||||
if (!m_in_render_pass)
|
||||
if (!InRenderPass())
|
||||
{
|
||||
GSVector4 clear_color = draw_rt ? draw_rt->GetUNormClearColor() : GSVector4::zero();
|
||||
if (pipe.ps.colclip_hw)
|
||||
@@ -4398,7 +4472,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
pipe.ps.blend_hw = config.blend_multi_pass.blend_hw;
|
||||
pipe.ps.dither = config.blend_multi_pass.dither;
|
||||
if (BindDrawPipeline(pipe))
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
// and the alpha pass
|
||||
@@ -4462,14 +4536,15 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig&
|
||||
GSTexture12* draw_ds, const bool feedback_rt, const bool feedback_depth, const bool one_barrier,
|
||||
const bool full_barrier)
|
||||
{
|
||||
if (BindDrawPipeline(pipe) && !m_features.texture_barrier) [[unlikely]]
|
||||
const int n_barriers = static_cast<int>(feedback_rt) + static_cast<int>(feedback_depth);
|
||||
|
||||
if (!m_features.texture_barrier) [[unlikely]]
|
||||
{
|
||||
DrawIndexedPrimitive();
|
||||
if (BindDrawPipeline(pipe))
|
||||
Draw(config);
|
||||
return;
|
||||
}
|
||||
|
||||
const int n_barriers = static_cast<int>(feedback_rt) + static_cast<int>(feedback_depth);
|
||||
|
||||
if (feedback_rt || feedback_depth)
|
||||
{
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
@@ -4502,7 +4577,7 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig&
|
||||
FeedbackBarrier(draw_ds);
|
||||
|
||||
if (BindDrawPipeline(pipe))
|
||||
DrawIndexedPrimitive(p, count);
|
||||
Draw(config, p, count);
|
||||
p += count;
|
||||
}
|
||||
|
||||
@@ -4514,14 +4589,14 @@ void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig&
|
||||
g_perfmon.Put(GSPerfMon::Barriers, n_barriers);
|
||||
|
||||
if (feedback_rt)
|
||||
FeedbackBarrier(draw_rt);
|
||||
FeedbackBarrier(draw_rt);
|
||||
if (feedback_depth)
|
||||
FeedbackBarrier(draw_ds);
|
||||
}
|
||||
}
|
||||
|
||||
if (BindDrawPipeline(pipe))
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
void GSDevice12::UpdateHWPipelineSelector(GSHWDrawConfig& config)
|
||||
@@ -4536,23 +4611,23 @@ void GSDevice12::UpdateHWPipelineSelector(GSHWDrawConfig& config)
|
||||
m_pipeline_selector.topology = static_cast<u32>(config.topology);
|
||||
m_pipeline_selector.rt = config.rt != nullptr;
|
||||
m_pipeline_selector.ds = config.ds != nullptr;
|
||||
m_pipeline_selector.ds_as_rt = config.ds_as_rt != nullptr;
|
||||
m_pipeline_selector.ds_as_rt = m_ds_as_rt != nullptr;
|
||||
}
|
||||
|
||||
void GSDevice12::UploadHWDrawVerticesAndIndices(GSHWDrawConfig& config)
|
||||
{
|
||||
IASetVertexBuffer(config.verts, sizeof(GSVertex), config.nverts);
|
||||
|
||||
// Update SRV in root signature directly, rather than using a uniform for base vertex.
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
m_dirty_flags |= DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING;
|
||||
|
||||
if (config.vs.UseExpandIndexBuffer())
|
||||
|
||||
if (config.vs.UseFixedExpandIndexBuffer())
|
||||
{
|
||||
m_index.start = 0;
|
||||
m_index.count = config.nindices;
|
||||
SetIndexBuffer(m_expand_index_buffer->GetGPUVirtualAddress(), EXPAND_BUFFER_SIZE, DXGI_FORMAT_R16_UINT);
|
||||
}
|
||||
else if (config.vs.UseVSExpandIndexBuffer())
|
||||
{
|
||||
VSSetIndexBuffer(config.indices, config.nindices);
|
||||
}
|
||||
else
|
||||
{
|
||||
IASetIndexBuffer(config.indices, config.nindices);
|
||||
|
||||
@@ -148,6 +148,7 @@ public:
|
||||
// Allocates a temporary CPU staging buffer, fires the callback with it to populate, then copies to a GPU buffer.
|
||||
bool AllocatePreinitializedGPUBuffer(u32 size, ID3D12Resource** gpu_buffer, D3D12MA::Allocation** gpu_allocation,
|
||||
const std::function<void(void*)>& fill_callback);
|
||||
void UploadIndices(D3D12StreamBuffer& buffer, const void* index, size_t count);
|
||||
|
||||
private:
|
||||
struct CommandListResources
|
||||
@@ -298,10 +299,12 @@ public:
|
||||
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_CBV = 0,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_CBV = 1,
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_SRV = 2,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_TEXTURES = 3,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_SAMPLERS = 4,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_RT_TEXTURES = 5,
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_VB_SRV = 2,
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_IB_SRV = 3,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_TEXTURES = 4,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_SAMPLERS = 5,
|
||||
TFX_ROOT_SIGNATURE_PARAM_PS_RT_TEXTURES = 6,
|
||||
TFX_ROOT_SIGNATURE_PARAM_VS_PUSH_CONSTANTS = 7,
|
||||
|
||||
UTILITY_ROOT_SIGNATURE_PARAM_PUSH_CONSTANTS = 0,
|
||||
UTILITY_ROOT_SIGNATURE_PARAM_PS_TEXTURES = 1,
|
||||
@@ -331,6 +334,7 @@ private:
|
||||
|
||||
D3D12StreamBuffer m_vertex_stream_buffer;
|
||||
D3D12StreamBuffer m_index_stream_buffer;
|
||||
D3D12StreamBuffer m_expand_index_stream_buffer;
|
||||
D3D12StreamBuffer m_vertex_constant_buffer;
|
||||
D3D12StreamBuffer m_pixel_constant_buffer;
|
||||
D3D12StreamBuffer m_texture_stream_buffer;
|
||||
@@ -365,6 +369,7 @@ private:
|
||||
|
||||
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
|
||||
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
|
||||
GSHWDrawConfig::VSPushConstants m_vs_pc_cache;
|
||||
|
||||
D3D12ShaderCache m_shader_cache;
|
||||
ComPtr<ID3DBlob> m_convert_vs;
|
||||
@@ -458,9 +463,15 @@ public:
|
||||
void PopDebugGroup() override;
|
||||
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
|
||||
|
||||
// Helpers and utility draws.
|
||||
void DrawPrimitive();
|
||||
void DrawIndexedPrimitive();
|
||||
void DrawIndexedPrimitive(int offset, int count);
|
||||
void DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion);
|
||||
|
||||
// Main GS primitive draws.
|
||||
void Draw(const GSHWDrawConfig& config);
|
||||
void Draw(const GSHWDrawConfig& config, int offset, int count);
|
||||
|
||||
std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override;
|
||||
|
||||
@@ -489,6 +500,7 @@ public:
|
||||
|
||||
void IASetVertexBuffer(const void* vertex, size_t stride, size_t count);
|
||||
void IASetIndexBuffer(const void* index, size_t count);
|
||||
void VSSetIndexBuffer(const void* index, size_t count);
|
||||
|
||||
void PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback = false);
|
||||
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
|
||||
@@ -498,6 +510,7 @@ public:
|
||||
|
||||
void SetVSConstantBuffer(const GSHWDrawConfig::VSConstantBuffer& cb);
|
||||
void SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb);
|
||||
void SetVSPushConstants(u32 base_vertex, u32 base_index = 0, bool force_update = false);
|
||||
bool BindDrawPipeline(const PipelineSelector& p);
|
||||
|
||||
void RenderHW(GSHWDrawConfig& config) override;
|
||||
@@ -568,29 +581,34 @@ private:
|
||||
DIRTY_FLAG_VS_CONSTANT_BUFFER_BINDING = (1 << 5),
|
||||
DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING = (1 << 6),
|
||||
DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING = (1 << 7),
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE = (1 << 8),
|
||||
DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE = (1 << 9),
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 = (1 << 10),
|
||||
DIRTY_FLAG_VS_INDEX_BUFFER_BINDING = (1 << 8),
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE = (1 << 9),
|
||||
DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE = (1 << 10),
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 = (1 << 11),
|
||||
DIRTY_FLAG_VS_PUSH_CONSTANTS = (1 << 12),
|
||||
|
||||
DIRTY_FLAG_VERTEX_BUFFER = (1 << 11),
|
||||
DIRTY_FLAG_INDEX_BUFFER = (1 << 12),
|
||||
DIRTY_FLAG_PRIMITIVE_TOPOLOGY = (1 << 13),
|
||||
DIRTY_FLAG_VIEWPORT = (1 << 14),
|
||||
DIRTY_FLAG_SCISSOR = (1 << 15),
|
||||
DIRTY_FLAG_RENDER_TARGET = (1 << 16),
|
||||
DIRTY_FLAG_PIPELINE = (1 << 17),
|
||||
DIRTY_FLAG_BLEND_CONSTANTS = (1 << 18),
|
||||
DIRTY_FLAG_STENCIL_REF = (1 << 19),
|
||||
DIRTY_FLAG_VERTEX_BUFFER = (1 << 13),
|
||||
DIRTY_FLAG_INDEX_BUFFER = (1 << 14),
|
||||
DIRTY_FLAG_PRIMITIVE_TOPOLOGY = (1 << 15),
|
||||
DIRTY_FLAG_VIEWPORT = (1 << 16),
|
||||
DIRTY_FLAG_SCISSOR = (1 << 17),
|
||||
DIRTY_FLAG_RENDER_TARGET = (1 << 18),
|
||||
DIRTY_FLAG_PIPELINE = (1 << 19),
|
||||
DIRTY_FLAG_BLEND_CONSTANTS = (1 << 20),
|
||||
DIRTY_FLAG_STENCIL_REF = (1 << 21),
|
||||
|
||||
DIRTY_BASE_STATE = DIRTY_FLAG_VS_CONSTANT_BUFFER_BINDING | DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING |
|
||||
DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING | DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE |
|
||||
DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE | DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 |
|
||||
DIRTY_FLAG_VERTEX_BUFFER | DIRTY_FLAG_INDEX_BUFFER |
|
||||
DIRTY_FLAG_PRIMITIVE_TOPOLOGY | DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR | DIRTY_FLAG_RENDER_TARGET |
|
||||
DIRTY_FLAG_PIPELINE | DIRTY_FLAG_BLEND_CONSTANTS | DIRTY_FLAG_STENCIL_REF,
|
||||
DIRTY_ROOT_PARAMS = DIRTY_FLAG_VS_CONSTANT_BUFFER_BINDING | DIRTY_FLAG_PS_CONSTANT_BUFFER_BINDING |
|
||||
DIRTY_FLAG_VS_VERTEX_BUFFER_BINDING | DIRTY_FLAG_VS_INDEX_BUFFER_BINDING |
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE | DIRTY_FLAG_SAMPLERS_DESCRIPTOR_TABLE |
|
||||
DIRTY_FLAG_TEXTURES_DESCRIPTOR_TABLE_2 | DIRTY_FLAG_VS_PUSH_CONSTANTS,
|
||||
|
||||
DIRTY_TFX_STATE = DIRTY_BASE_STATE | DIRTY_FLAG_TFX_TEXTURES | DIRTY_FLAG_TFX_SAMPLERS |
|
||||
DIRTY_BASE_STATE = DIRTY_FLAG_VERTEX_BUFFER | DIRTY_FLAG_INDEX_BUFFER | DIRTY_FLAG_PRIMITIVE_TOPOLOGY |
|
||||
DIRTY_FLAG_VIEWPORT | DIRTY_FLAG_SCISSOR | DIRTY_FLAG_RENDER_TARGET | DIRTY_FLAG_PIPELINE |
|
||||
DIRTY_FLAG_BLEND_CONSTANTS | DIRTY_FLAG_STENCIL_REF,
|
||||
|
||||
DIRTY_TFX_STATE = DIRTY_BASE_STATE | DIRTY_ROOT_PARAMS | DIRTY_FLAG_TFX_TEXTURES | DIRTY_FLAG_TFX_SAMPLERS |
|
||||
DIRTY_FLAG_TFX_RT_TEXTURES,
|
||||
|
||||
DIRTY_UTILITY_STATE = DIRTY_BASE_STATE,
|
||||
DIRTY_CONSTANT_BUFFER_STATE = DIRTY_FLAG_VS_CONSTANT_BUFFER | DIRTY_FLAG_PS_CONSTANT_BUFFER,
|
||||
};
|
||||
|
||||
@@ -50,8 +50,8 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
GIFRegTEX0 RTLookup = GIFRegTEX0::Create(RTBP0, RFBW, RFPSM);
|
||||
GSTextureCache::Source* src = g_texture_cache->LookupSource(true, RTLookup, r.m_cached_ctx.TEXA, r.m_cached_ctx.CLAMP, GSVector4i(0, 0, 1, 1), nullptr, true, false, r.m_cached_ctx.FRAME, true, true);
|
||||
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i(0, 0, 1, 1), true, false, true, src);
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupDrawTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, true, true, GSVector4i(0, 0, 1, 1), true, false, true, src);
|
||||
|
||||
if (!rt)
|
||||
return false;
|
||||
@@ -115,7 +115,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
// Detect the deswizzling shuffle from depth, copying the RG and BA separately on each half of the page (ignore the split).
|
||||
if (RTME && RFBP != RTBP0 && RFPSM == PSMCT16S && RTPSM == PSMCT16S)
|
||||
{
|
||||
if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index.tail == 128)
|
||||
if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index->tail == 128)
|
||||
{
|
||||
const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y/2, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y/2);
|
||||
const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y/2, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y/2);
|
||||
@@ -126,7 +126,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
}
|
||||
|
||||
// Following the previous draw, it tries to copy everything read from depth and offset it by 2, for the alternate line channel shuffle (skipped above).
|
||||
if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index.tail == 2)
|
||||
if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index->tail == 2)
|
||||
{
|
||||
GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y - 2.0f, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y - 2.0f);
|
||||
GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y);
|
||||
@@ -138,8 +138,8 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
|
||||
GIFRegTEX0 RTLookup = GIFRegTEX0::Create(RTBP0, RFBW, RFPSM);
|
||||
GSTextureCache::Source* src = g_texture_cache->LookupSource(true, RTLookup, r.m_cached_ctx.TEXA, r.m_cached_ctx.CLAMP, GSVector4i(0,0,1,1), nullptr, true, false, r.m_cached_ctx.FRAME, true, true);
|
||||
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i(0,0,1,1), true, false, true, src);
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupDrawTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, true, true, GSVector4i(0,0,1,1), true, false, true, src);
|
||||
|
||||
if (!rt)
|
||||
return false;
|
||||
@@ -264,10 +264,10 @@ bool GSHwHack::GSC_SFEX3(GSRendererHW& r, int& skip)
|
||||
// Skipping is no good as the copy is used again later, and it causes a weird shimmer/echo effect every other frame.
|
||||
|
||||
// Add on the height from the second part of the draw to the first, to make it one big rect.
|
||||
r.m_vertex.buff[1].XYZ.Y += r.m_vertex.buff[r.m_vertex.tail - 1].XYZ.Y - r.m_context->XYOFFSET.OFY;
|
||||
r.m_vertex.buff[1].V = r.m_vertex.buff[r.m_vertex.tail - 1].V;
|
||||
r.m_vertex.tail = 2;
|
||||
r.m_index.tail = 2;
|
||||
r.m_vertex->buff[1].XYZ.Y += r.m_vertex->buff[r.m_vertex->tail - 1].XYZ.Y - r.m_context->XYOFFSET.OFY;
|
||||
r.m_vertex->buff[1].V = r.m_vertex->buff[r.m_vertex->tail - 1].V;
|
||||
r.m_vertex->tail = 2;
|
||||
r.m_index->tail = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ bool GSHwHack::GSC_DTGames(GSRendererHW& r, int& skip)
|
||||
// The further problem to this is the limitation of alpha we can save on an RT as they copy in 255, so I can cheese it here pretending it's RTA'd
|
||||
if (RTME && RFPSM == PSMCT32 && RTBP0 == RFBP && RTPSM == PSMCT16 && RTEST.ATE && RTEST.ATST == ATST_NEVER && RTEST.AFAIL == AFAIL_FB_ONLY && RFBMSK == 0xFFFFFF)
|
||||
{
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupDrawTarget(GIFRegTEX0::Create(RTBP0, RFBW, RFPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
|
||||
|
||||
if (!rt)
|
||||
@@ -332,9 +332,9 @@ bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip)
|
||||
{
|
||||
if (skip == 0)
|
||||
{
|
||||
if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index.tail > 2)
|
||||
if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index->tail > 2)
|
||||
{
|
||||
GSVertex* v = &r.m_vertex.buff[0];
|
||||
GSVertex* v = &r.m_vertex->buff[0];
|
||||
// Don't enable hack on native res.
|
||||
// Fixes ghosting/blur effect and white lines appearing in stages: Moonfit Wilderness, Acid Rain - caused by upscaling.
|
||||
// Game copies the framebuffer as individual page rects with slight offsets (like 1/16 of a pixel etc) which doesn't wokr well with upscaling.
|
||||
@@ -348,7 +348,7 @@ bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip)
|
||||
else
|
||||
{
|
||||
// Fixes the alignment of the two halves for the heat haze on the temple stage.
|
||||
for (u32 i = 0; i < r.m_index.tail; i+=2)
|
||||
for (u32 i = 0; i < r.m_index->tail; i+=2)
|
||||
{
|
||||
v[i].XYZ.Y -= 0x8;
|
||||
}
|
||||
@@ -373,13 +373,13 @@ bool GSHwHack::GSC_SandGrainGames(GSRendererHW& r, int& skip)
|
||||
|
||||
if (r.PRIM->PRIM == GS_SPRITE && RTME && RFPSM == PSMCT16S && RTPSM == PSMZ16S && next_ctx.TEX0.TBP0 == RFBP && next_ctx.TEX0.PSM == PSMT8H)
|
||||
{
|
||||
GSTextureCache::Target* texsrc = g_texture_cache->LookupTarget(GIFRegTEX0::Create(RTBP0, RTBW, RTPSM),
|
||||
GSTextureCache::Target* texsrc = g_texture_cache->LookupDrawTarget(GIFRegTEX0::Create(RTBP0, RTBW, RTPSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil);
|
||||
|
||||
if (!texsrc)
|
||||
return false;
|
||||
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupTarget(GIFRegTEX0::Create(next_ctx.FRAME.Block(), next_ctx.FRAME.FBW, next_ctx.FRAME.PSM),
|
||||
GSTextureCache::Target* rt = g_texture_cache->LookupDrawTarget(GIFRegTEX0::Create(next_ctx.FRAME.Block(), next_ctx.FRAME.FBW, next_ctx.FRAME.PSM),
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
|
||||
|
||||
if (!rt)
|
||||
@@ -468,7 +468,7 @@ bool GSHwHack::GSC_BurnoutGames(GSRendererHW& r, int& skip)
|
||||
break;
|
||||
|
||||
// Next draw should contain our source.
|
||||
GSTextureCache::Target* tgt = g_texture_cache->LookupTarget(r.m_env.CTXT[r.m_backed_up_ctx].TEX0,
|
||||
GSTextureCache::Target* tgt = g_texture_cache->LookupDrawTarget(r.m_env.CTXT[r.m_backed_up_ctx].TEX0,
|
||||
GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget);
|
||||
if (!tgt)
|
||||
break;
|
||||
@@ -718,8 +718,8 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
|
||||
return false;
|
||||
}
|
||||
|
||||
GSTextureCache::Target* src = g_texture_cache->LookupTarget(RTEX0, GSVector2i(1, 1), r.GetTextureScaleFactor(),
|
||||
GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i::zero(), true);
|
||||
GSTextureCache::Target* src = g_texture_cache->LookupDrawTarget(RTEX0, GSVector2i(1, 1), r.GetTextureScaleFactor(),
|
||||
GSTextureCache::RenderTarget, true, 0, false, true, true, GSVector4i::zero(), true);
|
||||
if (!src)
|
||||
return false;
|
||||
|
||||
@@ -777,7 +777,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
|
||||
for (u32 channel = 0; channel < 3; channel++)
|
||||
{
|
||||
const GIFRegTEX0 TEX0 = GIFRegTEX0::Create(base + channel * page_offset, 10, PSMCT32);
|
||||
GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, src->GetUnscaledSize(), src->GetScale(), GSTextureCache::RenderTarget, true, fbmsk);
|
||||
GSTextureCache::Target* dst = g_texture_cache->LookupDrawTarget(TEX0, src->GetUnscaledSize(), src->GetScale(), GSTextureCache::RenderTarget, true, fbmsk);
|
||||
if (!dst)
|
||||
{
|
||||
dst = g_texture_cache->CreateTarget(TEX0, size, size, src->GetScale(), GSTextureCache::RenderTarget, true, fbmsk);
|
||||
@@ -822,7 +822,7 @@ bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip)
|
||||
GIFRegTEX0 TEX0 = {};
|
||||
TEX0.TBP0 = RFBP;
|
||||
TEX0.TBW = 8;
|
||||
GSTextureCache::Target* dst = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil);
|
||||
GSTextureCache::Target* dst = g_texture_cache->LookupDrawTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil);
|
||||
|
||||
if (!dst)
|
||||
dst = g_texture_cache->CreateTarget(TEX0, r.GetTargetSize(), r.GetValidSize(nullptr), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil,
|
||||
@@ -830,7 +830,7 @@ bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip)
|
||||
|
||||
if (dst)
|
||||
{
|
||||
float dc = r.m_vertex.buff[1].XYZ.Z;
|
||||
float dc = r.m_vertex->buff[1].XYZ.Z;
|
||||
g_gs_device->ClearDepth(dst->m_texture, dc * std::exp2(-32.0f));
|
||||
}
|
||||
}
|
||||
@@ -848,9 +848,9 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
|
||||
if (RPRIM->TME && RTEX0.TW == 3 && RTEX0.TH == 3 && RTEX0.PSM == 0 && RFRAME.FBMSK == 0x00FFFFFF && RFRAME.FBW == 8 && r.PCRTCDisplays.GetResolution().x > 512)
|
||||
{
|
||||
// Check we are drawing stripes
|
||||
for (u32 i = 1; i < r.m_vertex.tail; i+=2)
|
||||
for (u32 i = 1; i < r.m_vertex->tail; i+=2)
|
||||
{
|
||||
int value = (((r.m_vertex.buff[i].XYZ.X - r.m_vertex.buff[i - 1].XYZ.X) + 8) >> 4);
|
||||
int value = (((r.m_vertex->buff[i].XYZ.X - r.m_vertex->buff[i - 1].XYZ.X) + 8) >> 4);
|
||||
if (value != 32)
|
||||
return false;
|
||||
}
|
||||
@@ -862,18 +862,18 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
|
||||
|
||||
for (int vert = 32; vert < 40; vert+=2)
|
||||
{
|
||||
r.m_vertex.buff[vert].XYZ.X = context->XYOFFSET.OFX + (((vert * 16) << 4) - 8);
|
||||
r.m_vertex.buff[vert].XYZ.Y = context->XYOFFSET.OFY;
|
||||
r.m_vertex.buff[vert].U = (vert * 16) << 4;
|
||||
r.m_vertex.buff[vert].V = 0;
|
||||
r.m_vertex.buff[vert+1].XYZ.X = context->XYOFFSET.OFX + ((((vert * 16) + 32) << 4) - 8);
|
||||
r.m_vertex.buff[vert+1].XYZ.Y = context->XYOFFSET.OFY + (r.PCRTCDisplays.GetResolution().y << 4) + 8;
|
||||
r.m_vertex.buff[vert+1].U = ((vert * 16) + 32) << 4;
|
||||
r.m_vertex.buff[vert+1].V = r.PCRTCDisplays.GetResolution().y << 4;
|
||||
r.m_vertex->buff[vert].XYZ.X = context->XYOFFSET.OFX + (((vert * 16) << 4) - 8);
|
||||
r.m_vertex->buff[vert].XYZ.Y = context->XYOFFSET.OFY;
|
||||
r.m_vertex->buff[vert].U = (vert * 16) << 4;
|
||||
r.m_vertex->buff[vert].V = 0;
|
||||
r.m_vertex->buff[vert+1].XYZ.X = context->XYOFFSET.OFX + ((((vert * 16) + 32) << 4) - 8);
|
||||
r.m_vertex->buff[vert+1].XYZ.Y = context->XYOFFSET.OFY + (r.PCRTCDisplays.GetResolution().y << 4) + 8;
|
||||
r.m_vertex->buff[vert+1].U = ((vert * 16) + 32) << 4;
|
||||
r.m_vertex->buff[vert+1].V = r.PCRTCDisplays.GetResolution().y << 4;
|
||||
}
|
||||
|
||||
/*r.m_vertex.head = r.m_vertex.tail = r.m_vertex.next = 2;
|
||||
r.m_index.tail = 2;*/
|
||||
/*r.m_vertex->head = r.m_vertex->tail = r.m_vertex->next = 2;
|
||||
r.m_index->tail = 2;*/
|
||||
|
||||
r.m_vt.m_max.p.x = r.m_r.z;
|
||||
r.m_vt.m_max.p.y = r.m_r.w;
|
||||
@@ -907,7 +907,7 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
|
||||
|
||||
// This is the giant dither-like depth buffer. We need this on the CPU *and* the GPU for textures which are
|
||||
// rendered on both.
|
||||
if (context->FRAME.FBW == 8 && r.m_index.tail == 32 && r.PRIM->TME && context->TEX0.TBW == 1)
|
||||
if (context->FRAME.FBW == 8 && r.m_index->tail == 32 && r.PRIM->TME && context->TEX0.TBW == 1)
|
||||
{
|
||||
r.SwPrimRender(r, false, false);
|
||||
return false;
|
||||
@@ -947,8 +947,8 @@ bool GSHwHack::GSC_MetalGearSolid3(GSRendererHW& r, int& skip)
|
||||
GL_INS("OI_MetalGearSolid3(): %x -> %x, %dx%d, subtract %d", RFBP, RFBP + (RFBW / 2), r.m_r.width(), r.m_r.height(),
|
||||
w_sub);
|
||||
|
||||
for (u32 i = 0; i < r.m_vertex.next; i++)
|
||||
r.m_vertex.buff[i].XYZ.X -= w_sub_fp;
|
||||
for (u32 i = 0; i < r.m_vertex->next; i++)
|
||||
r.m_vertex->buff[i].XYZ.X -= w_sub_fp;
|
||||
|
||||
// No point adjusting the scissor, it just ends up expanding out anyway.. but we do have to fix up the draw rect.
|
||||
r.m_r -= GSVector4i(w_sub);
|
||||
@@ -961,7 +961,7 @@ bool GSHwHack::GSC_Turok(GSRendererHW& r, int& skip)
|
||||
// Since we can't look in to the future to check this, the options are either rearrange all the pages in a target when the width changes
|
||||
// (very slow, could break a ton of stuff which stores different things in the alpha channel), or this. I choose this.
|
||||
|
||||
if (r.m_index.tail == 6 && RPRIM->PRIM == 4 && !RTME && RFBMSK == 0x00FFFFFF && floor(r.m_vt.m_max.p.x) == 512 && r.m_env.CTXT[r.m_backed_up_ctx].FRAME.FBW == 10 && RFRAME.FBW == 8 && RFPSM == PSMCT32 && RTEST.ATE && RTEST.ATST == ATST_GEQUAL)
|
||||
if (r.m_index->tail == 6 && RPRIM->PRIM == 4 && !RTME && RFBMSK == 0x00FFFFFF && floor(r.m_vt.m_max.p.x) == 512 && r.m_env.CTXT[r.m_backed_up_ctx].FRAME.FBW == 10 && RFRAME.FBW == 8 && RFPSM == PSMCT32 && RTEST.ATE && RTEST.ATST == ATST_GEQUAL)
|
||||
{
|
||||
int num_pages = r.m_cached_ctx.FRAME.FBW * ((floor(r.m_vt.m_max.p.y) + 31) / 32);
|
||||
r.m_cached_ctx.FRAME.FBW = 10;
|
||||
@@ -978,7 +978,7 @@ bool GSHwHack::GSC_Turok(GSRendererHW& r, int& skip)
|
||||
|
||||
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
|
||||
{
|
||||
const u32 n_vertices = r.m_vertex.next;
|
||||
const u32 n_vertices = r.m_vertex->next;
|
||||
const int w = r.m_r.width();
|
||||
const int h = r.m_r.height();
|
||||
const bool is_copy = !r.PRIM->ABE || (
|
||||
@@ -1011,7 +1011,7 @@ bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds
|
||||
const u32 FBP = r.m_cached_ctx.FRAME.Block();
|
||||
const u32 FBW = r.m_cached_ctx.FRAME.FBW;
|
||||
GL_INS("PointListPalette - m_r = <%d, %d => %d, %d>, n_vertices = %u, FBP = 0x%x, FBW = %u", r.m_r.x, r.m_r.y, r.m_r.z, r.m_r.w, n_vertices, FBP, FBW);
|
||||
const GSVertex* RESTRICT v = r.m_vertex.buff;
|
||||
const GSVertex* RESTRICT v = r.m_vertex->buff;
|
||||
const int ox(r.m_context->XYOFFSET.OFX);
|
||||
const int oy(r.m_context->XYOFFSET.OFY);
|
||||
for (size_t i = 0; i < n_vertices; ++i)
|
||||
@@ -1067,7 +1067,7 @@ bool GSHwHack::OI_RozenMaidenGebetGarden(GSRendererHW& r, GSTexture* rt, GSTextu
|
||||
TEX0.TBW = RFRAME.FBW;
|
||||
TEX0.PSM = RFRAME.PSM;
|
||||
|
||||
if (GSTextureCache::Target* tmp_rt = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget))
|
||||
if (GSTextureCache::Target* tmp_rt = g_texture_cache->LookupDrawTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget))
|
||||
{
|
||||
GL_INS("OI_RozenMaidenGebetGarden FB clear");
|
||||
g_gs_device->ClearRenderTarget(tmp_rt->m_texture, 0);
|
||||
@@ -1089,7 +1089,7 @@ bool GSHwHack::OI_RozenMaidenGebetGarden(GSRendererHW& r, GSTexture* rt, GSTextu
|
||||
TEX0.TBW = RFRAME.FBW;
|
||||
TEX0.PSM = RZBUF.PSM;
|
||||
|
||||
if (GSTextureCache::Target* tmp_ds = g_texture_cache->LookupTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil))
|
||||
if (GSTextureCache::Target* tmp_ds = g_texture_cache->LookupDrawTarget(TEX0, r.GetTargetSize(), r.GetTextureScaleFactor(), GSTextureCache::DepthStencil))
|
||||
{
|
||||
GL_INS("OI_RozenMaidenGebetGarden ZB clear");
|
||||
g_gs_device->ClearDepth(tmp_ds->m_texture, 0.0f);
|
||||
@@ -1122,14 +1122,14 @@ bool GSHwHack::OI_SonicUnleashed(GSRendererHW& r, GSTexture* rt, GSTexture* ds,
|
||||
|
||||
GL_INS("OI_SonicUnleashed replace draw by a copy draw %lld", r.s_n);
|
||||
|
||||
GSTextureCache::Target* src = g_texture_cache->LookupTarget(Texture, GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, false, true, true, GSVector4i::zero(), true);
|
||||
GSTextureCache::Target* src = g_texture_cache->LookupDrawTarget(Texture, GSVector2i(1, 1), r.GetTextureScaleFactor(), GSTextureCache::RenderTarget, true, 0, false, true, true, GSVector4i::zero(), true);
|
||||
|
||||
if (!src)
|
||||
return true;
|
||||
|
||||
const GSVector2i src_size(src->m_texture->GetSize());
|
||||
|
||||
GSTextureCache::Target* rt_again = g_texture_cache->LookupTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget);
|
||||
GSTextureCache::Target* rt_again = g_texture_cache->LookupDrawTarget(Frame, src_size, src->m_scale, GSTextureCache::RenderTarget);
|
||||
if ((rt_again->m_TEX0.PSM & 0x3) == PSMCT16)
|
||||
{
|
||||
GSVector4 dRect;
|
||||
@@ -1209,9 +1209,9 @@ bool GSHwHack::OI_ArTonelico2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GST
|
||||
buffer to adapt the page width properly.
|
||||
*/
|
||||
|
||||
const GSVertex* v = &r.m_vertex.buff[0];
|
||||
const GSVertex* v = &r.m_vertex->buff[0];
|
||||
|
||||
if (ds && r.m_vertex.next == 2 && !RPRIM->TME && RFRAME.FBW == 10 && v->XYZ.Z == 0 && RTEST.ZTST == ZTST_ALWAYS)
|
||||
if (ds && r.m_vertex->next == 2 && !RPRIM->TME && RFRAME.FBW == 10 && v->XYZ.Z == 0 && RTEST.ZTST == ZTST_ALWAYS)
|
||||
{
|
||||
GL_INS("OI_ArTonelico2");
|
||||
g_gs_device->ClearDepth(ds, 0.0f);
|
||||
@@ -1284,15 +1284,15 @@ static bool GetMoveTargetPair(GSRendererHW& r, GSTextureCache::Target** src, GIF
|
||||
const int src_type =
|
||||
GSLocalMemory::m_psm[src_desc.PSM].depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget;
|
||||
GSTextureCache::Target* tsrc =
|
||||
g_texture_cache->LookupTarget(src_desc, GSVector2i(1, 1), r.GetTextureScaleFactor(), src_type);
|
||||
g_texture_cache->LookupDrawTarget(src_desc, GSVector2i(1, 1), r.GetTextureScaleFactor(), src_type);
|
||||
if (!tsrc)
|
||||
return false;
|
||||
|
||||
// The target might not.
|
||||
const int dst_type =
|
||||
GSLocalMemory::m_psm[dst_desc.PSM].depth ? GSTextureCache::DepthStencil : GSTextureCache::RenderTarget;
|
||||
GSTextureCache::Target* tdst = g_texture_cache->LookupTarget(dst_desc, tsrc->GetUnscaledSize(), tsrc->GetScale(),
|
||||
dst_type, true, 0, false, false, preserve_target, preserve_target, tsrc->GetUnscaledRect());
|
||||
GSTextureCache::Target* tdst = g_texture_cache->LookupDrawTarget(dst_desc, tsrc->GetUnscaledSize(), tsrc->GetScale(),
|
||||
dst_type, true, 0, false, preserve_target, preserve_target, tsrc->GetUnscaledRect());
|
||||
if (!tdst)
|
||||
{
|
||||
if (req_target)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -210,7 +210,7 @@ private:
|
||||
|
||||
void ResetStates();
|
||||
void HandleProvokingVertexFirst();
|
||||
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup);
|
||||
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);
|
||||
void EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptions& date_options, GSTextureCache::Target* rt,
|
||||
@@ -226,9 +226,11 @@ private:
|
||||
const TextureMinMaxResult& tmm);
|
||||
|
||||
void EmulateZbuffer(const GSTextureCache::Target* ds);
|
||||
void EmulateAA1();
|
||||
static void GetAlphaTestConfigPS(const u32 atst, const u8 aref, const bool invert_test, PS_ATST& ps_atst_out, float& aref_out);
|
||||
void EmulateAlphaTest(DATEOptions& date_options);
|
||||
void EmulateAlphaTestSecondPass();
|
||||
void ConfigureDepthFeedback();
|
||||
|
||||
void CalculateAlphaRange(GSTextureCache::Target* rt, GSTextureCache::Target* ds, DATEOptions& date_options,
|
||||
int& blend_alpha_min, int& blend_alpha_max, int& rt_new_alpha_min, int& rt_new_alpha_max);
|
||||
@@ -399,7 +401,6 @@ public:
|
||||
/// Compute the drawlist (if not already present) and bounding boxes for the current draw.
|
||||
std::size_t ComputeDrawlistGetSize(float scale);
|
||||
|
||||
/// Create a temporary color clone of depth for depth feedback (DX12 and GL only right now)
|
||||
void StartDepthAsRTFeedback();
|
||||
void CleanupDepthAsRTFeedback();
|
||||
/// Does the current draw allow using AA1 coverage (if AA1 is enabled).
|
||||
bool IsCoverageAlphaSupported() override;
|
||||
};
|
||||
|
||||
@@ -39,21 +39,21 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
|
||||
GSRasterizerData data;
|
||||
GSScanlineGlobalData& gd = data.global;
|
||||
|
||||
hw.m_sw_vertex_buffer.resize(((hw.m_vertex.next + 1) & ~1));
|
||||
hw.m_sw_vertex_buffer.resize(((hw.m_vertex->next + 1) & ~1));
|
||||
|
||||
data.primclass = vt.m_primclass;
|
||||
data.buff = nullptr;
|
||||
data.vertex = hw.m_sw_vertex_buffer.data();
|
||||
data.vertex_count = hw.m_vertex.next;
|
||||
data.index = hw.m_index.buff;
|
||||
data.index_count = hw.m_index.tail;
|
||||
data.vertex_count = hw.m_vertex->next;
|
||||
data.index = hw.m_index->buff;
|
||||
data.index_count = hw.m_index->tail;
|
||||
data.scanmsk_value = env.SCANMSK.MSK;
|
||||
|
||||
// Skip per pixel division if q is constant.
|
||||
// Optimize the division by 1 with a nop. It also means that GS_SPRITE_CLASS must be processed when !vt.m_eq.q.
|
||||
// If you have both GS_SPRITE_CLASS && vt.m_eq.q, it will depends on the first part of the 'OR'.
|
||||
const u32 q_div = !hw.IsMipMapActive() && ((vt.m_eq.q && vt.m_min.t.z != 1.0f) || (!vt.m_eq.q && vt.m_primclass == GS_SPRITE_CLASS));
|
||||
GSVertexSW::s_cvb[vt.m_primclass][PRIM->TME][PRIM->FST][q_div](context, data.vertex, hw.m_vertex.buff, hw.m_vertex.next);
|
||||
GSVertexSW::s_cvb[vt.m_primclass][PRIM->TME][PRIM->FST][q_div](context, data.vertex, hw.m_vertex->buff, hw.m_vertex->next);
|
||||
|
||||
GSVector4i scissor = context->scissor.in;
|
||||
GSVector4i bbox = GSVector4i(vt.m_min.p.floor().xyxy(vt.m_max.p.ceil())).rintersect(scissor);
|
||||
@@ -524,12 +524,12 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
|
||||
|
||||
const u32 ofx = context->XYOFFSET.OFX;
|
||||
|
||||
for (int i = 0, j = hw.m_vertex.tail; i < j; i++)
|
||||
for (int i = 0, j = hw.m_vertex->tail; i < j; i++)
|
||||
{
|
||||
#if _M_SSE >= 0x501
|
||||
if ((((hw.m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
|
||||
if ((((hw.m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
|
||||
#else
|
||||
if ((((hw.m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
|
||||
if ((((hw.m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
|
||||
#endif
|
||||
{
|
||||
gd.sel.notest = 0;
|
||||
@@ -551,13 +551,13 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
|
||||
if (add_ee_transfer)
|
||||
{
|
||||
GSRendererHW::GSUploadQueue uq;
|
||||
uq.transfer_type = GSRendererHW::GetInstance()->EEGS_TransferType::EE_to_GS;
|
||||
uq.blit.U64 = 0;
|
||||
uq.blit.DBP = hw.m_cached_ctx.FRAME.Block();
|
||||
uq.blit.DBW = hw.m_cached_ctx.FRAME.FBW;
|
||||
uq.blit.DPSM = hw.m_cached_ctx.FRAME.PSM;
|
||||
uq.draw = GSState::s_n;
|
||||
uq.rect = bbox;
|
||||
uq.zero_clear = false;
|
||||
hw.m_draw_transfers.push_back(uq);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,6 +30,8 @@ public:
|
||||
|
||||
constexpr static u32 MAX_BP = 0x3fff;
|
||||
|
||||
constexpr static GSVector4 FullSrcRect = GSVector4::cxpr(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
|
||||
constexpr static bool CheckOverlap(const u32 a_bp, const u32 a_bp_end, const u32 b_bp, const u32 b_bp_end) noexcept
|
||||
{
|
||||
u32 b_bp_start_synced = b_bp;
|
||||
@@ -261,7 +263,7 @@ public:
|
||||
|
||||
bool OverlapsValid(u32 bp, u32 bw, u32 psm, const GSVector4i& rect) const;
|
||||
|
||||
__fi bool HasValidAlpha() const { return (m_valid_alpha_low | m_valid_alpha_high); }
|
||||
__fi bool HasValidAlpha() const { return (m_valid_alpha_low || m_valid_alpha_high); }
|
||||
bool HasValidBitsForFormat(u32 psm, bool req_color, bool req_alpha, bool width_match);
|
||||
|
||||
void ResizeDrawn(const GSVector4i& rect);
|
||||
@@ -485,6 +487,20 @@ protected:
|
||||
|
||||
Source* CreateMergedSource(GIFRegTEX0 TEX0, GIFRegTEXA TEXA, SourceRegion region, float scale);
|
||||
|
||||
// Used in target lookup functions to determine new sizes of targets.
|
||||
struct RescaleHelper
|
||||
{
|
||||
RescaleHelper(const GSVector2i& size, float scale);
|
||||
void CalcRescale(const Target* tgt);
|
||||
void SetNewSize(const GSVector2i& new_size, float scale);
|
||||
|
||||
const GSVector2i m_size;
|
||||
float m_scale;
|
||||
GSVector2i m_new_size{};
|
||||
GSVector2i m_new_scaled_size{};
|
||||
GSVector4 m_dRect{};
|
||||
bool m_clear = true;
|
||||
};
|
||||
public:
|
||||
GSTextureCache();
|
||||
~GSTextureCache();
|
||||
@@ -516,14 +532,24 @@ public:
|
||||
|
||||
Target* FindTargetOverlap(Target* target, int type, int psm);
|
||||
void CombineAlignedInsideTargets(Target* target, GSTextureCache::Source* src = nullptr);
|
||||
Target* LookupTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0,
|
||||
bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_rgb = true, bool preserve_alpha = true,
|
||||
const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr, GSTextureCache::Target* ds = nullptr, int offset = -1);
|
||||
Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, float scale, int type, bool used = true, u32 fbmask = 0,
|
||||
bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true,
|
||||
|
||||
private:
|
||||
Target* ProcessTargetAfterLookup(RescaleHelper& rescaler, Target* dst, GIFRegTEX0 TEX0, const GSVector2i& size, int type,
|
||||
bool used, u32 fbmask, bool is_frame, bool preload, bool preserve_rgb = true, bool preserve_alpha = true,
|
||||
const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false,
|
||||
bool preserve_scale = false, GSTextureCache::Source* src = nullptr, GSTextureCache::Target* ds = nullptr, int offset = -1);
|
||||
|
||||
public:
|
||||
Target* CreateTarget(GIFRegTEX0 TEX0, const GSVector2i& size, const GSVector2i& valid_size, float scale, int type, bool used = true,
|
||||
u32 fbmask = 0, bool is_frame = false, bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_target = true,
|
||||
const GSVector4i draw_rc = GSVector4i::zero(), GSTextureCache::Source* src = nullptr);
|
||||
|
||||
Target* LookupDisplayTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, bool is_feedback);
|
||||
|
||||
Target* LookupDrawTarget(GIFRegTEX0 TEX0, const GSVector2i& size, float scale, int type, bool used = true, u32 fbmask = 0,
|
||||
bool preload = GSConfig.PreloadFrameWithGSData, bool preserve_rgb = true, bool preserve_alpha = true,
|
||||
const GSVector4i draw_rc = GSVector4i::zero(), bool is_shuffle = false, bool possible_clear = false, bool preserve_scale = false, GSTextureCache::Source* src = nullptr, GSTextureCache::Target* ds = nullptr, int offset = -1);
|
||||
|
||||
/// Looks up a target in the cache, and only returns it if the BP/BW match exactly.
|
||||
Target* GetExactTarget(u32 BP, u32 BW, int type, u32 end_bp);
|
||||
Target* GetTargetWithSharedBits(u32 BP, u32 PSM) const;
|
||||
|
||||
@@ -40,14 +40,15 @@ struct PipelineSelectorExtrasMTL
|
||||
GSDevice::BlendFactor dst_factor_alpha : 4;
|
||||
GSDevice::BlendOp blend_op : 2;
|
||||
bool blend_enable : 1;
|
||||
bool has_depth : 1;
|
||||
bool has_stencil : 1;
|
||||
bool has_depth : 1;
|
||||
bool has_stencil : 1;
|
||||
bool has_rt1 : 1;
|
||||
};
|
||||
u32 fullkey;
|
||||
};
|
||||
|
||||
PipelineSelectorExtrasMTL(): fullkey(0) {}
|
||||
PipelineSelectorExtrasMTL(GSHWDrawConfig::BlendState blend, GSTexture* rt, GSHWDrawConfig::ColorMaskSelector cms, bool has_depth, bool has_stencil)
|
||||
PipelineSelectorExtrasMTL(GSHWDrawConfig::BlendState blend, GSTexture* rt, GSHWDrawConfig::ColorMaskSelector cms, bool has_depth, bool has_stencil, bool has_rt1)
|
||||
: fullkey(0)
|
||||
{
|
||||
this->rt = rt ? rt->GetFormat() : GSTexture::Format::Invalid;
|
||||
@@ -65,6 +66,7 @@ struct PipelineSelectorExtrasMTL
|
||||
this->blend_enable = blend.enable;
|
||||
this->has_depth = has_depth;
|
||||
this->has_stencil = has_stencil;
|
||||
this->has_rt1 = has_rt1;
|
||||
}
|
||||
};
|
||||
struct PipelineSelectorMTL
|
||||
@@ -202,7 +204,7 @@ public:
|
||||
bool iip : 1;
|
||||
bool fst : 1;
|
||||
bool point_size : 1;
|
||||
GSShader::VSExpand expand : 2;
|
||||
GSShader::VSExpand expand : 3;
|
||||
};
|
||||
u8 key;
|
||||
};
|
||||
@@ -263,11 +265,11 @@ public:
|
||||
MRCOwned<id<MTLRenderPipelineState>> m_shadeboost_pipeline;
|
||||
MRCOwned<id<MTLRenderPipelineState>> m_imgui_pipeline;
|
||||
|
||||
MRCOwned<id<MTLFunction>> m_hw_vs[1 << 5];
|
||||
MRCOwned<id<MTLFunction>> m_hw_vs[6 << 3];
|
||||
std::unordered_map<PSSelector, MRCOwned<id<MTLFunction>>> m_hw_ps;
|
||||
std::unordered_map<PipelineSelectorMTL, MRCOwned<id<MTLRenderPipelineState>>> m_hw_pipeline;
|
||||
|
||||
MRCOwned<MTLRenderPassDescriptor*> m_render_pass_desc[8];
|
||||
MRCOwned<MTLRenderPassDescriptor*> m_render_pass_desc[16];
|
||||
|
||||
MRCOwned<id<MTLSamplerState>> m_sampler_hw[1 << 8];
|
||||
|
||||
@@ -287,8 +289,9 @@ public:
|
||||
GSTexture* color_target = nullptr;
|
||||
GSTexture* depth_target = nullptr;
|
||||
GSTexture* stencil_target = nullptr;
|
||||
GSTexture* tex[8] = {};
|
||||
void* vertex_buffer = nullptr;
|
||||
GSTexture* tex[GSMTLTextureIndexCount] = {};
|
||||
id<MTLBuffer> vertex_buffer = nullptr;
|
||||
id<MTLBuffer> vs_index_buffer = nullptr;
|
||||
void* name = nullptr;
|
||||
struct Has
|
||||
{
|
||||
@@ -298,6 +301,7 @@ public:
|
||||
bool blend_color : 1;
|
||||
bool pipeline_sel : 1;
|
||||
bool sampler : 1;
|
||||
bool rt1_depth : 1;
|
||||
} has = {};
|
||||
DepthStencilSelector depth_sel = DepthStencilSelector::NoDepth();
|
||||
// Clear line (Things below here are tracked by `has` and don't need to be cleared to reset)
|
||||
@@ -315,6 +319,8 @@ public:
|
||||
MRCOwned<id<MTLBlitCommandEncoder>> m_late_texture_upload_encoder;
|
||||
MRCOwned<id<MTLCommandBuffer>> m_vertex_upload_cmdbuf;
|
||||
MRCOwned<id<MTLBlitCommandEncoder>> m_vertex_upload_encoder;
|
||||
id<MTLTexture> m_ds_as_rt_texture = nil;
|
||||
GSTexture* m_ds_as_rt_gstexture = nullptr;
|
||||
|
||||
struct DebugEntry
|
||||
{
|
||||
@@ -347,6 +353,8 @@ public:
|
||||
id<MTLCommandBuffer> GetRenderCmdBufWithoutCreate();
|
||||
/// Get the spin fence if spinning is enabled.
|
||||
id<MTLFence> GetSpinFence();
|
||||
/// Get the texture to use as RT1 depth for the given depth texture
|
||||
id<MTLTexture> GetRT1DepthTexture(GSTextureMTL* depth);
|
||||
/// Called by command buffers when they finish
|
||||
void DrawCommandBufferFinished(u64 draw, id<MTLCommandBuffer> buffer);
|
||||
/// Flush pending operations from all encoders to the GPU
|
||||
@@ -356,7 +364,7 @@ public:
|
||||
/// End current render pass without flushing
|
||||
void EndRenderPass();
|
||||
/// 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);
|
||||
void BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil = nullptr, MTLLoadAction stencil_load = MTLLoadActionDontCare, bool rt1 = false);
|
||||
/// Call at the end of each frame
|
||||
void FrameCompleted();
|
||||
|
||||
@@ -412,6 +420,7 @@ public:
|
||||
void UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize) override;
|
||||
void ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM) override;
|
||||
void FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect) override;
|
||||
void BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea) override;
|
||||
|
||||
void FlushClears(GSTexture* tex);
|
||||
|
||||
@@ -422,6 +431,7 @@ public:
|
||||
void MRESetSampler(SamplerSelector sel);
|
||||
void MRESetTexture(GSTexture* tex, int pos);
|
||||
void MRESetVertices(id<MTLBuffer> buffer, size_t offset);
|
||||
void MRESetVSIndices(id<MTLBuffer> buffer, size_t offset);
|
||||
void MRESetScissor(const GSVector4i& scissor);
|
||||
void MREClearScissor();
|
||||
void MRESetCB(const GSHWDrawConfig::VSConstantBuffer& cb_vs);
|
||||
|
||||
@@ -35,14 +35,7 @@ std::vector<GSAdapterInfo> GetMetalAdapterList()
|
||||
{
|
||||
GSAdapterInfo ai;
|
||||
ai.name = [[dev name] UTF8String];
|
||||
|
||||
ai.max_texture_size = 8192;
|
||||
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
|
||||
ai.max_texture_size = 16384;
|
||||
if (@available(macOS 10.15, iOS 13.0, *))
|
||||
if ([dev supportsFamily:MTLGPUFamilyApple3])
|
||||
ai.max_texture_size = 16384;
|
||||
|
||||
ai.max_texture_size = GSMTLDevice::GetMaxTextureSize(dev);
|
||||
ai.max_upscale_multiplier = GSGetMaxUpscaleMultiplier(ai.max_texture_size);
|
||||
list.push_back(std::move(ai));
|
||||
}
|
||||
@@ -102,6 +95,11 @@ GSDeviceMTL::GSDeviceMTL()
|
||||
|
||||
GSDeviceMTL::~GSDeviceMTL()
|
||||
{
|
||||
// m_ds_as_rt_texture is owned if the device has memoryless textures
|
||||
if (m_dev.features.memoryless_textures)
|
||||
[m_ds_as_rt_texture release];
|
||||
else if (m_ds_as_rt_gstexture)
|
||||
delete m_ds_as_rt_gstexture;
|
||||
}
|
||||
|
||||
GSDeviceMTL::Map GSDeviceMTL::Allocate(UploadBuffer& buffer, size_t amt)
|
||||
@@ -252,6 +250,14 @@ id<MTLFence> GSDeviceMTL::GetSpinFence()
|
||||
return m_spin_timer ? m_spin_fence : nil;
|
||||
}
|
||||
|
||||
id<MTLTexture> GSDeviceMTL::GetRT1DepthTexture(GSTextureMTL* depth)
|
||||
{
|
||||
if (m_dev.features.framebuffer_fetch)
|
||||
return m_ds_as_rt_texture;
|
||||
else
|
||||
return static_cast<GSTextureMTL*>(m_ds_as_rt)->GetTexture();
|
||||
}
|
||||
|
||||
void GSDeviceMTL::DrawCommandBufferFinished(u64 draw, id<MTLCommandBuffer> buffer)
|
||||
{
|
||||
// We can do the update non-atomically because we only ever update under the lock
|
||||
@@ -395,14 +401,15 @@ void GSDeviceMTL::EndRenderPass()
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil, MTLLoadAction stencil_load)
|
||||
void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadAction color_load, GSTexture* depth, MTLLoadAction depth_load, GSTexture* stencil, MTLLoadAction stencil_load, bool rt1)
|
||||
{
|
||||
GSTextureMTL* mc = static_cast<GSTextureMTL*>(color);
|
||||
GSTextureMTL* md = static_cast<GSTextureMTL*>(depth);
|
||||
GSTextureMTL* ms = static_cast<GSTextureMTL*>(stencil);
|
||||
bool needs_new = color != m_current_render.color_target
|
||||
|| depth != m_current_render.depth_target
|
||||
|| stencil != m_current_render.stencil_target;
|
||||
|| stencil != m_current_render.stencil_target
|
||||
|| rt1 != m_current_render.has.rt1_depth;
|
||||
GSVector4 color_clear;
|
||||
float depth_clear;
|
||||
// Depth and stencil might be the same, so do all invalidation checks before resetting invalidation
|
||||
@@ -456,6 +463,7 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
|
||||
if (mc) idx |= 1;
|
||||
if (md) idx |= 2;
|
||||
if (ms) idx |= 4;
|
||||
if (rt1) idx |= 8;
|
||||
|
||||
MTLRenderPassDescriptor* desc = m_render_pass_desc[idx];
|
||||
if (mc)
|
||||
@@ -481,6 +489,14 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
|
||||
pxAssert(stencil_load != MTLLoadActionClear);
|
||||
desc.stencilAttachment.loadAction = stencil_load;
|
||||
}
|
||||
if (rt1)
|
||||
{
|
||||
pxAssert(md);
|
||||
MTLRenderPassColorAttachmentDescriptor* color1 = desc.colorAttachments[1];
|
||||
color1.texture = GetRT1DepthTexture(md);
|
||||
if (m_features.framebuffer_fetch)
|
||||
color1.clearColor = MTLClearColorMake(depth_load == MTLLoadActionClear ? depth_clear : -1, 0, 0, 0);
|
||||
}
|
||||
|
||||
EndRenderPass();
|
||||
m_current_render.encoder = MRCRetain([GetRenderCmdBuf() renderCommandEncoderWithDescriptor:desc]);
|
||||
@@ -492,6 +508,7 @@ void GSDeviceMTL::BeginRenderPass(NSString* name, GSTexture* color, MTLLoadActio
|
||||
m_current_render.color_target = color;
|
||||
m_current_render.depth_target = depth;
|
||||
m_current_render.stencil_target = stencil;
|
||||
m_current_render.has.rt1_depth |= rt1;
|
||||
pxAssertRel(m_current_render.encoder, "Failed to create render encoder!");
|
||||
}
|
||||
|
||||
@@ -845,7 +862,7 @@ static MRCOwned<id<MTLSamplerState>> CreateSampler(id<MTLDevice> dev, GSHWDrawCo
|
||||
[sdesc setTAddressMode:sel.tav ? MTLSamplerAddressModeRepeat : MTLSamplerAddressModeClampToEdge];
|
||||
[sdesc setRAddressMode:MTLSamplerAddressModeClampToEdge];
|
||||
|
||||
[sdesc setMaxAnisotropy:GSConfig.MaxAnisotropy && sel.aniso ? GSConfig.MaxAnisotropy : 1];
|
||||
[sdesc setMaxAnisotropy:1];
|
||||
bool clampLOD = sel.lodclamp || !sel.UseMipmapFiltering();
|
||||
const char* clampdesc = clampLOD ? " LODClamp" : "";
|
||||
[sdesc setLodMaxClamp:clampLOD ? 0.25f : FLT_MAX];
|
||||
@@ -856,6 +873,20 @@ static MRCOwned<id<MTLSamplerState>> CreateSampler(id<MTLDevice> dev, GSHWDrawCo
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool getDepthFeedback(const GSMTLDevice& dev, bool fbfetch)
|
||||
{
|
||||
switch (GSConfig.DepthFeedbackMode)
|
||||
{
|
||||
case GSDepthFeedbackMode::Auto:
|
||||
return dev.features.depth_feedback;
|
||||
case GSDepthFeedbackMode::Depth:
|
||||
// Depth feedback + FBFetch not supported
|
||||
return !fbfetch;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
{ @autoreleasepool {
|
||||
if (!GSDevice::Create(vsync_mode, allow_present_throttle))
|
||||
@@ -942,12 +973,14 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
m_features.stencil_buffer = true;
|
||||
m_features.cas_sharpening = true;
|
||||
m_features.test_and_sample_depth = true;
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
m_features.depth_feedback = getDepthFeedback(m_dev, m_features.framebuffer_fetch);
|
||||
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand;
|
||||
m_max_texture_size = m_dev.features.max_texsize;
|
||||
|
||||
// Init metal stuff
|
||||
m_fn_constants = MRCTransfer([MTLFunctionConstantValues new]);
|
||||
setFnConstantB(m_fn_constants, m_dev.features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH);
|
||||
setFnConstantB(m_fn_constants, m_features.framebuffer_fetch, GSMTLConstantIndex_FRAMEBUFFER_FETCH);
|
||||
setFnConstantB(m_fn_constants, m_features.depth_feedback, GSMTLConstantIndex_DEPTH_FEEDBACK);
|
||||
|
||||
m_draw_sync_fence = MRCTransfer([m_dev.dev newFence]);
|
||||
[m_draw_sync_fence setLabel:@"Draw Sync Fence"];
|
||||
@@ -983,11 +1016,27 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
applyAttribute(m_hw_vertex, GSMTLAttributeIndexUV, MTLVertexFormatUShort2, offsetof(GSVertex, UV), GSMTLBufferIndexHWVertices);
|
||||
applyAttribute(m_hw_vertex, GSMTLAttributeIndexF, MTLVertexFormatUChar4Normalized, offsetof(GSVertex, FOG), GSMTLBufferIndexHWVertices);
|
||||
|
||||
for (auto& desc : m_render_pass_desc)
|
||||
for (size_t i = 0; i < std::size(m_render_pass_desc); i++)
|
||||
{
|
||||
desc = MRCTransfer([MTLRenderPassDescriptor new]);
|
||||
const bool depth = i & 2;
|
||||
const bool stencil = i & 4;
|
||||
const bool rt1 = i & 8;
|
||||
if (rt1 && m_features.depth_feedback)
|
||||
continue;
|
||||
if (rt1 && !depth)
|
||||
continue;
|
||||
if (stencil && m_features.framebuffer_fetch)
|
||||
continue;
|
||||
auto desc = MRCTransfer([MTLRenderPassDescriptor new]);
|
||||
[[desc depthAttachment] setStoreAction:MTLStoreActionStore];
|
||||
[[desc stencilAttachment] setStoreAction:MTLStoreActionStore];
|
||||
if (rt1)
|
||||
{
|
||||
MTLRenderPassColorAttachmentDescriptor* color1 = [[desc colorAttachments] objectAtIndexedSubscript:1];
|
||||
[color1 setStoreAction:MTLStoreActionDontCare];
|
||||
[color1 setLoadAction:m_features.framebuffer_fetch ? MTLLoadActionClear : MTLLoadActionLoad];
|
||||
}
|
||||
m_render_pass_desc[i] = desc;
|
||||
}
|
||||
|
||||
// Init samplers
|
||||
@@ -1062,13 +1111,8 @@ bool GSDeviceMTL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
setFnConstantB(m_fn_constants, sel.fst, GSMTLConstantIndex_FST);
|
||||
setFnConstantB(m_fn_constants, sel.iip, GSMTLConstantIndex_IIP);
|
||||
setFnConstantB(m_fn_constants, sel.point_size, GSMTLConstantIndex_VS_POINT_SIZE);
|
||||
NSString* shader = @"vs_main";
|
||||
if (sel.expand != GSShader::VSExpand::None)
|
||||
{
|
||||
setFnConstantI(m_fn_constants, sel.expand, GSMTLConstantIndex_VS_EXPAND_TYPE);
|
||||
shader = @"vs_main_expand";
|
||||
}
|
||||
m_hw_vs[i] = LoadShader(shader);
|
||||
setFnConstantI(m_fn_constants, sel.expand, GSMTLConstantIndex_VS_EXPAND_TYPE);
|
||||
m_hw_vs[i] = LoadShader(sel.expand == GSShader::VSExpand::None ? @"vs_main" : @"vs_main_expand");
|
||||
}
|
||||
|
||||
// Init pipelines
|
||||
@@ -1273,12 +1317,14 @@ bool GSDeviceMTL::SupportsExclusiveFullscreen() const { return false; }
|
||||
std::string GSDeviceMTL::GetDriverInfo() const
|
||||
{ @autoreleasepool {
|
||||
std::string desc([[m_dev.dev description] UTF8String]);
|
||||
desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported");
|
||||
desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported");
|
||||
desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported");
|
||||
desc += "\n Primitive ID: " + std::string(m_dev.features.primid ? "Supported" : "Unsupported");
|
||||
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);
|
||||
desc += "\n Texture Swizzle: " + std::string(m_dev.features.texture_swizzle ? "Supported" : "Unsupported");
|
||||
desc += "\n Unified Memory: " + std::string(m_dev.features.unified_memory ? "Supported" : "Unsupported");
|
||||
desc += "\n Framebuffer Fetch: " + std::string(m_dev.features.framebuffer_fetch ? "Supported" : "Unsupported");
|
||||
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 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;
|
||||
}}
|
||||
|
||||
@@ -1764,6 +1810,50 @@ void GSDeviceMTL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3
|
||||
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, pipeline, false, LoadAction::DontCareIfFull, &uniform, sizeof(uniform));
|
||||
}}
|
||||
|
||||
static id<MTLTexture> CreateDSAsRTTexture(id<MTLDevice> dev, NSUInteger width, NSUInteger height, MTLStorageMode storage, NSString* name)
|
||||
{
|
||||
MTLTextureDescriptor *desc = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatR32Float width:width height:height mipmapped:false];
|
||||
[desc setUsage:MTLTextureUsageRenderTarget];
|
||||
[desc setStorageMode:storage];
|
||||
id<MTLTexture> result = [dev newTextureWithDescriptor:desc];
|
||||
[result setLabel:name];
|
||||
return result;
|
||||
}
|
||||
|
||||
void GSDeviceMTL::BeginDSAsRT(GSTexture* ds, const GSVector4i& drawarea)
|
||||
{
|
||||
if (!m_features.framebuffer_fetch)
|
||||
return GSDevice::BeginDSAsRT(ds, drawarea);
|
||||
u32 needed_width = ds->GetWidth();
|
||||
u32 needed_height = ds->GetHeight();
|
||||
u32 current_width = static_cast<u32>([m_ds_as_rt_texture width]);
|
||||
u32 current_height = static_cast<u32>([m_ds_as_rt_texture height]);
|
||||
if (m_dev.features.memoryless_textures)
|
||||
{
|
||||
if (needed_width > current_width || needed_height > current_height) [[unlikely]] @autoreleasepool
|
||||
{
|
||||
u32 width = std::max(needed_width, current_width);
|
||||
u32 height = std::max(needed_height, current_height);
|
||||
[m_ds_as_rt_texture release];
|
||||
m_ds_as_rt_texture = CreateDSAsRTTexture(m_dev.dev, width, height, MTLStorageModeMemoryless, @"DS as RT");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (needed_width == current_width && needed_height == current_height)
|
||||
return;
|
||||
if (m_ds_as_rt_gstexture)
|
||||
Recycle(m_ds_as_rt_gstexture);
|
||||
m_ds_as_rt_gstexture = CreateRenderTarget(needed_width, needed_height, GSTexture::Format::Float32, false, true);
|
||||
m_ds_as_rt_texture = static_cast<GSTextureMTL*>(m_ds_as_rt_gstexture)->GetTexture();
|
||||
@autoreleasepool
|
||||
{
|
||||
NSString* name = [NSString stringWithFormat:@"DS as RT %dx%d", needed_width, needed_height];
|
||||
[m_ds_as_rt_texture setLabel:name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceMTL::FlushClears(GSTexture* tex)
|
||||
{
|
||||
if (tex)
|
||||
@@ -1807,7 +1897,7 @@ static MTLBlendOperation ConvertBlendOp(GSDevice::BlendOp generic)
|
||||
|
||||
void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDrawConfig::PSSelector pssel, GSHWDrawConfig::BlendState blend, GSHWDrawConfig::ColorMaskSelector cms)
|
||||
{
|
||||
PipelineSelectorExtrasMTL extras(blend, m_current_render.color_target, cms, m_current_render.depth_target, m_current_render.stencil_target);
|
||||
PipelineSelectorExtrasMTL extras(blend, m_current_render.color_target, cms, m_current_render.depth_target, m_current_render.stencil_target, m_current_render.has.rt1_depth);
|
||||
PipelineSelectorMTL fullsel(vssel, pssel, extras);
|
||||
if (m_current_render.has.pipeline_sel && fullsel == m_current_render.pipeline_sel)
|
||||
return;
|
||||
@@ -1849,6 +1939,7 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
|
||||
setFnConstantI(m_fn_constants, pssel.date, GSMTLConstantIndex_PS_DATE);
|
||||
setFnConstantI(m_fn_constants, pssel.atst, GSMTLConstantIndex_PS_ATST);
|
||||
setFnConstantI(m_fn_constants, pssel.afail, GSMTLConstantIndex_PS_AFAIL);
|
||||
setFnConstantI(m_fn_constants, pssel.ztst, GSMTLConstantIndex_PS_ZTST);
|
||||
setFnConstantI(m_fn_constants, pssel.tfx, GSMTLConstantIndex_PS_TFX);
|
||||
setFnConstantB(m_fn_constants, pssel.tcc, GSMTLConstantIndex_PS_TCC);
|
||||
setFnConstantI(m_fn_constants, pssel.wms, GSMTLConstantIndex_PS_WMS);
|
||||
@@ -1893,6 +1984,9 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
|
||||
setFnConstantB(m_fn_constants, pssel.manual_lod, GSMTLConstantIndex_PS_MANUAL_LOD);
|
||||
setFnConstantB(m_fn_constants, pssel.region_rect, GSMTLConstantIndex_PS_REGION_RECT);
|
||||
setFnConstantI(m_fn_constants, pssel.scanmsk, GSMTLConstantIndex_PS_SCANMSK);
|
||||
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");
|
||||
ps = newps;
|
||||
m_hw_ps.insert(std::make_pair(pssel, std::move(newps)));
|
||||
@@ -1927,6 +2021,11 @@ void GSDeviceMTL::MRESetHWPipelineState(GSHWDrawConfig::VSSelector vssel, GSHWDr
|
||||
color.sourceAlphaBlendFactor = ConvertBlendFactor(extras.src_factor_alpha);
|
||||
color.destinationAlphaBlendFactor = ConvertBlendFactor(extras.dst_factor_alpha);
|
||||
}
|
||||
if (extras.has_rt1)
|
||||
{
|
||||
MTLRenderPipelineColorAttachmentDescriptor* color1 = [[pdesc colorAttachments] objectAtIndexedSubscript:1];
|
||||
[color1 setPixelFormat:MTLPixelFormatR32Float];
|
||||
}
|
||||
NSString* pname = [NSString stringWithFormat:@"HW Render %x.%x.%llx.%x", vssel_mtl.key, pssel.key_hi, pssel.key_lo, extras.fullkey];
|
||||
auto pipeline = MakePipeline(pdesc, vs, ps, pname);
|
||||
|
||||
@@ -1980,9 +2079,9 @@ void GSDeviceMTL::MRESetTexture(GSTexture* tex, int pos)
|
||||
|
||||
void GSDeviceMTL::MRESetVertices(id<MTLBuffer> buffer, size_t offset)
|
||||
{
|
||||
if (m_current_render.vertex_buffer != (__bridge void*)buffer)
|
||||
if (m_current_render.vertex_buffer != buffer)
|
||||
{
|
||||
m_current_render.vertex_buffer = (__bridge void*)buffer;
|
||||
m_current_render.vertex_buffer = buffer;
|
||||
[m_current_render.encoder setVertexBuffer:buffer offset:offset atIndex:GSMTLBufferIndexHWVertices];
|
||||
}
|
||||
else
|
||||
@@ -1991,6 +2090,19 @@ void GSDeviceMTL::MRESetVertices(id<MTLBuffer> buffer, size_t offset)
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceMTL::MRESetVSIndices(id<MTLBuffer> buffer, size_t offset)
|
||||
{
|
||||
if (m_current_render.vs_index_buffer != buffer)
|
||||
{
|
||||
m_current_render.vs_index_buffer = buffer;
|
||||
[m_current_render.encoder setVertexBuffer:buffer offset:offset atIndex:GSMTLBufferIndexHWIndices];
|
||||
}
|
||||
else
|
||||
{
|
||||
[m_current_render.encoder setVertexBufferOffset:offset atIndex:GSMTLBufferIndexHWIndices];
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceMTL::MRESetScissor(const GSVector4i& scissor)
|
||||
{
|
||||
if (m_current_render.has.scissor && (m_current_render.scissor == scissor).alltrue())
|
||||
@@ -2130,6 +2242,8 @@ void GSDeviceMTL::MREInitHWDraw(GSHWDrawConfig& config, const Map& verts)
|
||||
MRESetCB(config.cb_vs);
|
||||
MRESetCB(config.cb_ps);
|
||||
MRESetVertices(verts.gpu_buffer, verts.gpu_offset);
|
||||
if (config.vs.UseVSExpandIndexBuffer())
|
||||
MRESetVSIndices(verts.gpu_buffer, verts.gpu_offset + config.nverts * sizeof(*config.verts));
|
||||
}
|
||||
|
||||
void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
@@ -2138,22 +2252,31 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
EndRenderPass(); // Barrier
|
||||
|
||||
size_t vertsize = config.nverts * sizeof(*config.verts);
|
||||
size_t idxsize = config.vs.UseExpandIndexBuffer() ? 0 : (config.nindices * sizeof(*config.indices));
|
||||
size_t idxsize = config.vs.UseFixedExpandIndexBuffer() ? 0 : (config.nindices * sizeof(*config.indices));
|
||||
Map allocation = Allocate(m_vertex_upload_buf, vertsize + idxsize);
|
||||
memcpy(allocation.cpu_buffer, config.verts, vertsize);
|
||||
|
||||
id<MTLBuffer> index_buffer;
|
||||
size_t index_buffer_offset;
|
||||
if (!config.vs.UseExpandIndexBuffer())
|
||||
id<MTLBuffer> index_buffer = nil;
|
||||
size_t index_buffer_offset = 0;
|
||||
if (!config.vs.UseFixedExpandIndexBuffer())
|
||||
{
|
||||
memcpy(static_cast<u8*>(allocation.cpu_buffer) + vertsize, config.indices, idxsize);
|
||||
index_buffer = allocation.gpu_buffer;
|
||||
index_buffer_offset = allocation.gpu_offset + vertsize;
|
||||
if (config.vs.UseVSExpandIndexBuffer())
|
||||
{
|
||||
// VS expand index buffer is bound to the VS instead of the input assembler
|
||||
u32 expand = GetExpansionFactor(config.vs.expand);
|
||||
config.nindices *= expand;
|
||||
config.indices_per_prim *= expand;
|
||||
}
|
||||
else
|
||||
{
|
||||
index_buffer = allocation.gpu_buffer;
|
||||
index_buffer_offset = allocation.gpu_offset + vertsize;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
index_buffer = m_expand_index_buffer;
|
||||
index_buffer_offset = 0;
|
||||
}
|
||||
|
||||
FlushClears(config.tex);
|
||||
@@ -2273,7 +2396,9 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
return;
|
||||
}
|
||||
|
||||
BeginRenderPass(@"RenderHW", rt, MTLLoadActionLoad, config.ds, MTLLoadActionLoad, stencil, MTLLoadActionLoad);
|
||||
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);
|
||||
id<MTLRenderCommandEncoder> mtlenc = m_current_render.encoder;
|
||||
FlushDebugEntries(mtlenc);
|
||||
if (usesStencil(config.destination_alpha))
|
||||
@@ -2281,6 +2406,11 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
MREInitHWDraw(config, allocation);
|
||||
if (config.require_one_barrier || config.require_full_barrier)
|
||||
MRESetTexture(rt, GSMTLTextureIndexRenderTarget);
|
||||
if (feedback_depth)
|
||||
{
|
||||
GSTexture* tex = !m_features.depth_feedback && !m_features.framebuffer_fetch ? m_ds_as_rt : config.ds;
|
||||
MRESetTexture(tex, GSMTLTextureIndexDepthTarget);
|
||||
}
|
||||
if (primid_tex)
|
||||
MRESetTexture(primid_tex, GSMTLTextureIndexPrimIDs);
|
||||
if (config.blend.constant_enable)
|
||||
@@ -2313,7 +2443,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
|
||||
Recycle(colclip_rt);
|
||||
|
||||
g_gs_device->SetColorClipTexture(nullptr);
|
||||
SetColorClipTexture(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2321,6 +2451,24 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
Recycle(primid_tex);
|
||||
}}
|
||||
|
||||
static void EncodeDraw(id<MTLRenderCommandEncoder> enc, MTLPrimitiveType topology, size_t count, id<MTLBuffer> indices, size_t off, size_t base_vertex)
|
||||
{
|
||||
if (indices)
|
||||
{
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:count
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:indices
|
||||
indexBufferOffset:off + base_vertex * sizeof(uint16_t)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[enc drawPrimitives:topology
|
||||
vertexStart:base_vertex
|
||||
vertexCount:count];
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder> enc, id<MTLBuffer> buffer, size_t off, bool one_barrier, bool full_barrier)
|
||||
{
|
||||
MTLPrimitiveType topology;
|
||||
@@ -2333,14 +2481,8 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
|
||||
if (!m_features.texture_barrier) [[unlikely]]
|
||||
{
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:config.nindices
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off];
|
||||
|
||||
EncodeDraw(enc, topology, config.nindices, buffer, off, 0);
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2373,13 +2515,9 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
|
||||
for (u32 n = 0, p = 0; n < draw_list_size; n++)
|
||||
{
|
||||
const u32 count = (*config.drawlist)[n] * indices_per_prim;
|
||||
const size_t count = (*config.drawlist)[n] * indices_per_prim;
|
||||
textureBarrier(enc);
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:count
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off + p * sizeof(*config.indices)];
|
||||
EncodeDraw(enc, topology, count, buffer, off, p);
|
||||
p += count;
|
||||
}
|
||||
|
||||
@@ -2393,12 +2531,7 @@ void GSDeviceMTL::SendHWDraw(GSHWDrawConfig& config, id<MTLRenderCommandEncoder>
|
||||
g_perfmon.Put(GSPerfMon::Barriers, 1);
|
||||
}
|
||||
|
||||
[enc drawIndexedPrimitives:topology
|
||||
indexCount:config.nindices
|
||||
indexType:MTLIndexTypeUInt16
|
||||
indexBuffer:buffer
|
||||
indexBufferOffset:off];
|
||||
|
||||
EncodeDraw(enc, topology, config.nindices, buffer, off, 0);
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "common/MRCHelpers.h"
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "GS/Renderers/Common/GSShaderEnums.h"
|
||||
#include <Metal/Metal.h>
|
||||
|
||||
struct GSMTLDevice
|
||||
@@ -25,12 +26,14 @@ struct GSMTLDevice
|
||||
|
||||
struct Features
|
||||
{
|
||||
bool unified_memory;
|
||||
bool texture_swizzle;
|
||||
bool framebuffer_fetch;
|
||||
bool primid;
|
||||
bool slow_color_compression; ///< Color compression seems to slow down rt read on AMD
|
||||
bool has_fast_half;
|
||||
bool unified_memory : 1;
|
||||
bool texture_swizzle : 1;
|
||||
bool framebuffer_fetch : 1;
|
||||
bool primid : 1;
|
||||
bool slow_color_compression : 1; ///< Color compression seems to slow down rt read on AMD
|
||||
bool has_fast_half : 1;
|
||||
bool memoryless_textures : 1;
|
||||
bool depth_feedback : 1;
|
||||
MetalVersion shader_version;
|
||||
int max_texsize;
|
||||
};
|
||||
@@ -42,6 +45,8 @@ struct GSMTLDevice
|
||||
GSMTLDevice() = default;
|
||||
explicit GSMTLDevice(MRCOwned<id<MTLDevice>> dev);
|
||||
|
||||
static u32 GetMaxTextureSize(id<MTLDevice> dev);
|
||||
|
||||
bool IsOk() const { return dev && shaders; }
|
||||
void Reset()
|
||||
{
|
||||
|
||||
@@ -147,7 +147,7 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
|
||||
|
||||
if (@available(macOS 11.0, iOS 13.0, *))
|
||||
if ([dev supportsFamily:MTLGPUFamilyApple1])
|
||||
features.framebuffer_fetch = true;
|
||||
features.framebuffer_fetch = features.memoryless_textures = true;
|
||||
|
||||
if (@available(macOS 10.15, iOS 13.0, *))
|
||||
if ([dev supportsFamily:MTLGPUFamilyMac2] || [dev supportsFamily:MTLGPUFamilyApple1])
|
||||
@@ -164,21 +164,49 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
|
||||
if (features.primid && !detectPrimIDSupport(dev, shaders))
|
||||
features.primid = false;
|
||||
|
||||
if (!features.framebuffer_fetch && features.shader_version >= MetalVersion::Metal23)
|
||||
features.depth_feedback = false;
|
||||
|
||||
NSString* name = [dev name];
|
||||
if ([name containsString:@"Intel"])
|
||||
{
|
||||
switch (detectIntelGPU(dev, shaders))
|
||||
if (!features.framebuffer_fetch && features.shader_version >= MetalVersion::Metal23)
|
||||
{
|
||||
case DetectionResult::HaswellOrNotIntel:
|
||||
break;
|
||||
case DetectionResult::Broadwell:
|
||||
features.primid = false; // Broken
|
||||
break;
|
||||
case DetectionResult::Skylake:
|
||||
features.primid = false; // Broken
|
||||
features.framebuffer_fetch = true;
|
||||
break;
|
||||
switch (detectIntelGPU(dev, shaders))
|
||||
{
|
||||
case DetectionResult::HaswellOrNotIntel:
|
||||
// Older Intel GPUs seem to be fine with depth feedback
|
||||
features.depth_feedback = true;
|
||||
break;
|
||||
case DetectionResult::Broadwell:
|
||||
features.primid = false; // Broken
|
||||
break;
|
||||
case DetectionResult::Skylake:
|
||||
features.primid = false; // Broken
|
||||
features.framebuffer_fetch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ([name containsString:@"AMD"])
|
||||
{
|
||||
// RDNA+ seems to work fine with depth feedback
|
||||
if (@available(macOS 13, iOS 16, *))
|
||||
if ([dev supportsFamily:MTLGPUFamilyMetal3])
|
||||
features.depth_feedback = true;
|
||||
}
|
||||
else if ([name containsString:@"NVIDIA"])
|
||||
{
|
||||
// macOS only supports Kepler, which seems to work fine with depth feedback
|
||||
features.depth_feedback = true;
|
||||
}
|
||||
else if ([name containsString:@"Apple"])
|
||||
{
|
||||
// No special settings
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Warning("Unrecognized GPU vendor %s", [name UTF8String]);
|
||||
}
|
||||
|
||||
if (features.framebuffer_fetch && GSConfig.DisableFramebufferFetch)
|
||||
{
|
||||
@@ -191,16 +219,26 @@ GSMTLDevice::GSMTLDevice(MRCOwned<id<MTLDevice>> dev)
|
||||
else
|
||||
features.slow_color_compression = [[dev name] containsString:@"AMD"] || [[dev name] isEqualToString:@"Intel HD Graphics 4000"];
|
||||
|
||||
features.max_texsize = 8192;
|
||||
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
|
||||
features.max_texsize = 16384;
|
||||
if (@available(macOS 10.15, iOS 13.0, *))
|
||||
if ([dev supportsFamily:MTLGPUFamilyApple3])
|
||||
features.max_texsize = 16384;
|
||||
features.max_texsize = GetMaxTextureSize(dev);
|
||||
|
||||
this->dev = std::move(dev);
|
||||
}
|
||||
|
||||
u32 GSMTLDevice::GetMaxTextureSize(id<MTLDevice> dev)
|
||||
{
|
||||
if (@available(macOS 10.15, iOS 13.0, *))
|
||||
{
|
||||
MTLGPUFamily apple10 = static_cast<MTLGPUFamily>(1010); // Avoid relying on latest SDK, define ourselves
|
||||
if ([dev supportsFamily:apple10])
|
||||
return 32768;
|
||||
if ([dev supportsFamily:MTLGPUFamilyApple3])
|
||||
return 16384;
|
||||
}
|
||||
if ([dev supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
|
||||
return 16384;
|
||||
return 8192;
|
||||
}
|
||||
|
||||
const char* to_string(GSMTLDevice::MetalVersion ver)
|
||||
{
|
||||
switch (ver)
|
||||
|
||||
@@ -11,6 +11,7 @@ enum GSMTLBufferIndices
|
||||
GSMTLBufferIndexUniforms,
|
||||
GSMTLBufferIndexHWVertices,
|
||||
GSMTLBufferIndexHWUniforms,
|
||||
GSMTLBufferIndexHWIndices,
|
||||
};
|
||||
|
||||
enum GSMTLTextureIndex
|
||||
@@ -20,6 +21,8 @@ enum GSMTLTextureIndex
|
||||
GSMTLTextureIndexPalette,
|
||||
GSMTLTextureIndexRenderTarget,
|
||||
GSMTLTextureIndexPrimIDs,
|
||||
GSMTLTextureIndexDepthTarget,
|
||||
GSMTLTextureIndexCount,
|
||||
};
|
||||
|
||||
struct GSMTLConvertPSUniform
|
||||
@@ -95,6 +98,7 @@ struct GSMTLMainVSUniform
|
||||
vector_float2 texture_offset;
|
||||
vector_float2 point_size;
|
||||
uint max_depth;
|
||||
uint _pad0;
|
||||
};
|
||||
|
||||
struct GSMTLMainPSUniform
|
||||
@@ -153,6 +157,7 @@ enum GSMTLFnConstants
|
||||
{
|
||||
GSMTLConstantIndex_CAS_SHARPEN_ONLY,
|
||||
GSMTLConstantIndex_FRAMEBUFFER_FETCH,
|
||||
GSMTLConstantIndex_DEPTH_FEEDBACK,
|
||||
GSMTLConstantIndex_FST,
|
||||
GSMTLConstantIndex_IIP,
|
||||
GSMTLConstantIndex_VS_POINT_SIZE,
|
||||
@@ -167,6 +172,7 @@ enum GSMTLFnConstants
|
||||
GSMTLConstantIndex_PS_DATE,
|
||||
GSMTLConstantIndex_PS_ATST,
|
||||
GSMTLConstantIndex_PS_AFAIL,
|
||||
GSMTLConstantIndex_PS_ZTST,
|
||||
GSMTLConstantIndex_PS_TFX,
|
||||
GSMTLConstantIndex_PS_TCC,
|
||||
GSMTLConstantIndex_PS_WMS,
|
||||
@@ -211,4 +217,7 @@ enum GSMTLFnConstants
|
||||
GSMTLConstantIndex_PS_MANUAL_LOD,
|
||||
GSMTLConstantIndex_PS_REGION_RECT,
|
||||
GSMTLConstantIndex_PS_SCANMSK,
|
||||
GSMTLConstantIndex_PS_AA1,
|
||||
GSMTLConstantIndex_PS_ABE,
|
||||
GSMTLConstantIndex_PS_SW_ANISO,
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ constant uint SHUFFLE_READ = 1;
|
||||
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 FST [[function_constant(GSMTLConstantIndex_FST)]];
|
||||
constant bool IIP [[function_constant(GSMTLConstantIndex_IIP)]];
|
||||
constant bool VS_POINT_SIZE [[function_constant(GSMTLConstantIndex_VS_POINT_SIZE)]];
|
||||
@@ -26,6 +27,7 @@ constant bool PS_FOG [[function_constant(GSMTLConstantIndex_PS_FO
|
||||
constant uint PS_DATE [[function_constant(GSMTLConstantIndex_PS_DATE)]];
|
||||
constant uint PS_ATST_RAW [[function_constant(GSMTLConstantIndex_PS_ATST)]];
|
||||
constant uint PS_AFAIL_RAW [[function_constant(GSMTLConstantIndex_PS_AFAIL)]];
|
||||
constant uint PS_ZTST_RAW [[function_constant(GSMTLConstantIndex_PS_ZTST)]];
|
||||
constant uint PS_TFX [[function_constant(GSMTLConstantIndex_PS_TFX)]];
|
||||
constant bool PS_TCC [[function_constant(GSMTLConstantIndex_PS_TCC)]];
|
||||
constant uint PS_WMS [[function_constant(GSMTLConstantIndex_PS_WMS)]];
|
||||
@@ -70,13 +72,20 @@ constant bool PS_AUTOMATIC_LOD [[function_constant(GSMTLConstantIndex_PS_AU
|
||||
constant bool PS_MANUAL_LOD [[function_constant(GSMTLConstantIndex_PS_MANUAL_LOD)]];
|
||||
constant bool PS_REGION_RECT [[function_constant(GSMTLConstantIndex_PS_REGION_RECT)]];
|
||||
constant uint PS_SCANMSK [[function_constant(GSMTLConstantIndex_PS_SCANMSK)]];
|
||||
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)]];
|
||||
|
||||
using GSShader::VSExpand;
|
||||
using AFAIL = GSShader::PS_AFAIL;
|
||||
using ATST = GSShader::PS_ATST;
|
||||
using GSShader::ZTST;
|
||||
using AA1 = GSShader::PS_AA1;
|
||||
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);
|
||||
|
||||
#if defined(__METAL_MACOS__) && __METAL_VERSION__ >= 220
|
||||
#define PRIMID_SUPPORT 1
|
||||
@@ -103,12 +112,24 @@ constant bool SW_BLEND = (PS_BLEND_A != PS_BLEND_B) || PS_BLEND_D;
|
||||
constant bool SW_AD_TO_HW = (PS_BLEND_C == 1 && PS_A_MASKED);
|
||||
constant bool NEEDS_RT_FOR_BLEND = (((PS_BLEND_A != PS_BLEND_B) && (PS_BLEND_A == 1 || PS_BLEND_B == 1 || PS_BLEND_C == 1)) || PS_BLEND_D == 1 || SW_AD_TO_HW);
|
||||
constant bool NEEDS_RT_EARLY = PS_TEX_IS_FB || PS_DATE >= 5;
|
||||
constant bool NEEDS_RT_FOR_AFAIL = PS_AFAIL == AFAIL::ZB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY;
|
||||
constant bool NEEDS_RT_FOR_AFAIL = PS_AFAIL == AFAIL::ZB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY_SW_Z;
|
||||
constant bool NEEDS_RT = NEEDS_RT_FOR_AFAIL || NEEDS_RT_EARLY || (!PS_PRIM_CHECKING_INIT && (PS_FBMASK || NEEDS_RT_FOR_BLEND));
|
||||
constant bool NEEDS_DEPTH_FOR_AFAIL = PS_AFAIL == AFAIL::FB_ONLY || PS_AFAIL == AFAIL::RGB_ONLY_SW_Z;
|
||||
constant bool NEEDS_DEPTH_FOR_ZTST = PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER;
|
||||
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;
|
||||
constant bool PS_ZOUTPUT = PS_ZCLAMP || PS_ZFLOOR || SW_DEPTH;
|
||||
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;
|
||||
constant bool VS_NEEDS_INDEX_BUFFER = VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
||||
constant bool VS_COVERAGE = VS_EXPAND_TYPE == VSExpand::LineAA1 || VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
||||
constant bool VS_INTERIOR = VS_EXPAND_TYPE == VSExpand::TriangleAA1;
|
||||
constant bool PS_COVERAGE = PS_AA1 != AA1::NONE;
|
||||
constant bool PS_INTERIOR = PS_AA1 == AA1::TRIANGLE_SW_Z;
|
||||
|
||||
struct MainVSIn
|
||||
{
|
||||
@@ -128,6 +149,8 @@ struct MainVSOut
|
||||
float4 ti;
|
||||
float4 c [[function_constant(IIP)]];
|
||||
float4 fc [[flat, function_constant(NOT_IIP)]];
|
||||
float inv_cov [[function_constant(VS_COVERAGE)]];
|
||||
uint interior [[function_constant(VS_INTERIOR)]];
|
||||
float point_size [[point_size, function_constant(VS_POINT_SIZE)]];
|
||||
};
|
||||
|
||||
@@ -138,13 +161,26 @@ struct MainPSIn
|
||||
float4 ti;
|
||||
float4 c [[function_constant(IIP)]];
|
||||
float4 fc [[flat, function_constant(NOT_IIP)]];
|
||||
float inv_cov [[function_constant(PS_COVERAGE)]];
|
||||
uint interior [[function_constant(PS_INTERIOR)]];
|
||||
};
|
||||
|
||||
struct MainPSOut
|
||||
{
|
||||
float4 c0 [[color(0), index(0), function_constant(PS_COLOR0)]];
|
||||
float4 c1 [[color(0), index(1), function_constant(PS_COLOR1)]];
|
||||
float depth [[depth(less), function_constant(PS_ZOUTPUT)]];
|
||||
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)
|
||||
{
|
||||
if (PS_ZOUTPUT_LESS)
|
||||
depthLess = depth;
|
||||
if (PS_ZOUTPUT_ANY)
|
||||
depthAny = depth;
|
||||
if (PS_ZOUTPUT_COLOR)
|
||||
depthColor = depth;
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Vertex functions
|
||||
@@ -220,7 +256,8 @@ static MainVSIn load_vertex(GSMTLMainVertex base)
|
||||
vertex MainVSOut vs_main_expand(
|
||||
uint vid [[vertex_id]],
|
||||
device const GSMTLMainVertex* vertices [[buffer(GSMTLBufferIndexHWVertices)]],
|
||||
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]])
|
||||
constant GSMTLMainVSUniform& cb [[buffer(GSMTLBufferIndexHWUniforms)]],
|
||||
device const ushort* indices [[buffer(GSMTLBufferIndexHWIndices), function_constant(VS_NEEDS_INDEX_BUFFER)]])
|
||||
{
|
||||
switch (VS_EXPAND_TYPE)
|
||||
{
|
||||
@@ -236,6 +273,7 @@ vertex MainVSOut vs_main_expand(
|
||||
return point;
|
||||
}
|
||||
case VSExpand::Line:
|
||||
case VSExpand::LineAA1:
|
||||
{
|
||||
uint vid_base = vid >> 2;
|
||||
bool is_bottom = vid & 2;
|
||||
@@ -244,13 +282,27 @@ vertex MainVSOut vs_main_expand(
|
||||
MainVSOut point = vs_main_run(load_vertex(vertices[vid_base]), cb);
|
||||
MainVSOut other = vs_main_run(load_vertex(vertices[vid_other]), cb);
|
||||
|
||||
float2 line_vector = normalize(point.p.xy - other.p.xy);
|
||||
float2 line_normal = float2(line_vector.y, -line_vector.x);
|
||||
float2 line_width = (line_normal * cb.point_size) / 2;
|
||||
// line_normal is inverted for bottom point
|
||||
float2 offset = (is_bottom ^ is_right) ? line_width : -line_width;
|
||||
// Use bottom minus top for delta regardless of which vertex we are expanding.
|
||||
float2 line_delta = is_bottom ? point.p.xy - other.p.xy : other.p.xy - point.p.xy;
|
||||
float2 line_expand;
|
||||
if (VS_EXPAND_TYPE == VSExpand::Line)
|
||||
{
|
||||
float2 line_vector = normalize(line_delta);
|
||||
line_expand = float2(line_vector.y, -line_vector.x);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expand in y direction for shallow lines and x direction for steep lines.
|
||||
line_delta /= cb.vertex_scale;
|
||||
line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0, 2) : float2(2, 0);
|
||||
}
|
||||
float2 line_width = (line_expand * cb.point_size) / 2;
|
||||
float2 offset = is_right ? line_width : -line_width;
|
||||
point.p.xy += offset;
|
||||
|
||||
if (VS_EXPAND_TYPE == VSExpand::LineAA1)
|
||||
point.inv_cov = is_right ? 1.f : -1.f;
|
||||
|
||||
// Lines will be run as (0 1 2) (1 2 3)
|
||||
// This means that both triangles will have a point based off the top line point as their first point
|
||||
// So we don't have to do anything for !IIP
|
||||
@@ -284,6 +336,68 @@ vertex MainVSOut vs_main_expand(
|
||||
out.ti.yw = lt.ti.yw;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
case VSExpand::TriangleAA1:
|
||||
{
|
||||
// Triangles with AA1 are expanded as follows:
|
||||
// - Vertices 0-2: Interior of triangle (1 triangle).
|
||||
// - Vertices 3-8: First edge expanded (2 triangles).
|
||||
// - Vertices 9-14: Second edge expanded (2 triangles).
|
||||
// - Vertices 15-20: Third edge expanded (2 triangles).
|
||||
|
||||
uint prim_id = vid / 21;
|
||||
uint prim_offset = vid - 21 * prim_id; // range: 0-20
|
||||
bool interior = prim_offset < 3;
|
||||
uint i0 = interior ? prim_offset : (prim_offset - 3) / 6;
|
||||
uint i1 = (i0 >= 2) ? i0 - 2 : i0 + 1;
|
||||
uint i2 = (i0 >= 1) ? i0 - 1 : i0 + 2;
|
||||
MainVSOut out = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i0]]), cb);
|
||||
MainVSOut other = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i1]]), cb);
|
||||
MainVSOut opposite = vs_main_run(load_vertex(vertices[indices[3 * prim_id + i2]]), cb);
|
||||
if (interior)
|
||||
{
|
||||
out.inv_cov = 0.f;
|
||||
out.interior = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertex indices for this edge. We need all 3 for determining exterior/interior.
|
||||
uint prim_offset_edges = prim_offset - 3; // range: 0-17
|
||||
uint edge_offset = prim_offset_edges - 6 * i0; // range: 0-5
|
||||
|
||||
// Note: order of top/bottom, inside/outside order is arbitrary,
|
||||
// as long as it assembles into two triangles forming a quad.
|
||||
bool is_bottom = (2 <= edge_offset) && (edge_offset <= 4);
|
||||
bool is_outside = (edge_offset & 1) != 0;
|
||||
|
||||
// Similar expansion to line AA1 except instead of expanding on both sides of
|
||||
// the line we expand on on the side towards the outside of the triangle.
|
||||
float2 line_delta = out.p.xy - other.p.xy;
|
||||
float2 line_normal = normalize(float2(line_delta.y, -line_delta.x));
|
||||
float2 line_expand = abs(line_delta.x) >= abs(line_delta.y) ? float2(0, 2) : float2(2, 0);
|
||||
if ((dot(line_expand, line_normal) >= 0.0f) == (dot(opposite.p.xy - out.p.xy, line_normal) >= 0.0f))
|
||||
{
|
||||
// Expand direction point towards the interior so flip it.
|
||||
line_expand = -line_expand;
|
||||
}
|
||||
float2 line_width = (line_expand * cb.point_size) / 2;
|
||||
|
||||
if (is_bottom)
|
||||
out = other;
|
||||
if (is_outside)
|
||||
{
|
||||
out.p.xy += line_width;
|
||||
out.inv_cov = 1.0f; // No coverage
|
||||
}
|
||||
else
|
||||
{
|
||||
out.inv_cov = 0.0f; // Full coverage
|
||||
}
|
||||
|
||||
out.interior = 0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -299,6 +413,7 @@ struct PSMain
|
||||
texture2d<float> prim_id_tex;
|
||||
sampler tex_sampler;
|
||||
float4 current_color;
|
||||
float current_depth;
|
||||
uint prim_id;
|
||||
const thread MainPSIn& in;
|
||||
constant GSMTLMainPSUniform& cb;
|
||||
@@ -322,6 +437,154 @@ struct PSMain
|
||||
return tex.read(pos, lod);
|
||||
}
|
||||
|
||||
uint2 get_tex_dims()
|
||||
{
|
||||
if (PS_TEX_IS_DEPTH)
|
||||
return uint2(tex_depth.get_width(), tex_depth.get_height());
|
||||
else
|
||||
return uint2(tex.get_width(), tex.get_height());
|
||||
}
|
||||
|
||||
float manual_lod(float uv_w)
|
||||
{
|
||||
// FIXME add LOD: K - ( LOG2(Q) * (1 << L))
|
||||
float K = cb.lod_params.x;
|
||||
float L = cb.lod_params.y;
|
||||
float bias = cb.lod_params.z;
|
||||
float max_lod = cb.lod_params.w;
|
||||
|
||||
float gs_lod = K - log2(abs(uv_w)) * L;
|
||||
// FIXME max useful ?
|
||||
//return max(min(gs_lod, max_lod) - bias, 0.0f);
|
||||
return min(gs_lod, max_lod) - bias;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// 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_inf_nan = any(isinf(dX) | isinf(dY) | isnan(dX) | isnan(dY));
|
||||
|
||||
if (!(d_zero || d_par || d_per || d_inf_nan))
|
||||
{
|
||||
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);
|
||||
|
||||
float2 new_dX = float2(
|
||||
sqrt(F * (t + p) / (t * (q + t))),
|
||||
sqrt(F * (t - p) / (t * (q + t))) * sign(B)
|
||||
);
|
||||
|
||||
float2 new_dY = float2(
|
||||
sqrt(F * (t - p) / (t * (q - t))) * -sign(B),
|
||||
sqrt(F * (t + p) / (t * (q - t)))
|
||||
);
|
||||
|
||||
d_inf_nan = any(isinf(new_dX) | isinf(new_dY) | isnan(new_dX) | isnan(new_dY));
|
||||
if (!d_inf_nan)
|
||||
{
|
||||
dX = new_dX;
|
||||
dY = new_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);
|
||||
|
||||
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.
|
||||
// A small length_major would result in aniso_ratio getting clamped to 1.
|
||||
// Perform isotropic filtering instead.
|
||||
aniso_ratio = 1.0f;
|
||||
length_lod = length_major;
|
||||
aniso_line = float2(0, 0);
|
||||
}
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
float lod = PS_AUTOMATIC_LOD ? log2(length_lod) : PS_MANUAL_LOD ? manual_lod(uv_w) : 0;
|
||||
|
||||
float4 colour;
|
||||
if (aniso_ratio == 1.0f)
|
||||
{
|
||||
colour = sample_tex(tex_sampler, uv, level(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;
|
||||
float2 uv_sample = uv + d;
|
||||
float4 sample_colour = sample_tex(tex_sampler, uv_sample, level(lod));
|
||||
num += sample_colour;
|
||||
}
|
||||
|
||||
colour = num / aniso_ratio;
|
||||
}
|
||||
return colour;
|
||||
}
|
||||
|
||||
float4 sample_c(float2 uv)
|
||||
{
|
||||
if (PS_TEX_IS_FB)
|
||||
@@ -345,28 +608,14 @@ struct PSMain
|
||||
uv.y = uv.y * cb.st_scale.y;
|
||||
}
|
||||
|
||||
if (PS_AUTOMATIC_LOD)
|
||||
{
|
||||
if (PS_SW_ANISO > 1)
|
||||
return sample_c_af(uv, in.t.w);
|
||||
else if (PS_AUTOMATIC_LOD)
|
||||
return sample_tex(tex_sampler, uv);
|
||||
}
|
||||
else if (PS_MANUAL_LOD)
|
||||
{
|
||||
float K = cb.lod_params.x;
|
||||
float L = cb.lod_params.y;
|
||||
float bias = cb.lod_params.z;
|
||||
float max_lod = cb.lod_params.w;
|
||||
|
||||
float gs_lod = K - log2(abs(in.t.w)) * L;
|
||||
// FIXME max useful ?
|
||||
//float lod = max(min(gs_lod, max_lod) - bias, 0.f);
|
||||
float lod = min(gs_lod, max_lod) - bias;
|
||||
|
||||
return sample_tex(tex_sampler, uv, level(lod));
|
||||
}
|
||||
return sample_tex(tex_sampler, uv, level(manual_lod(in.t.w)));
|
||||
else
|
||||
{
|
||||
return sample_tex(tex_sampler, uv, level(0));
|
||||
}
|
||||
}
|
||||
|
||||
float4 sample_p(uint idx)
|
||||
@@ -1061,6 +1310,17 @@ struct PSMain
|
||||
MainPSOut ps_main()
|
||||
{
|
||||
MainPSOut out = {};
|
||||
float input_z = in.p.z;
|
||||
if (PS_ZFLOOR)
|
||||
input_z = floor(input_z * 0x1p32) * 0x1p-32;
|
||||
|
||||
if (PS_ZTST == ZTST::GEQUAL || PS_ZTST == ZTST::GREATER)
|
||||
{
|
||||
if (PS_ZTST == ZTST::GEQUAL && input_z < current_depth)
|
||||
discard_fragment();
|
||||
if (PS_ZTST == ZTST::GREATER && input_z <= current_depth)
|
||||
discard_fragment();
|
||||
}
|
||||
|
||||
if (PS_SCANMSK & 2)
|
||||
{
|
||||
@@ -1088,18 +1348,25 @@ struct PSMain
|
||||
}
|
||||
|
||||
float4 C = ps_color();
|
||||
bool atst_pass = atst(C);
|
||||
if (PS_AFAIL == GSShader::PS_AFAIL::KEEP && !atst_pass)
|
||||
discard_fragment();
|
||||
|
||||
// Must be done before alpha correction
|
||||
|
||||
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
||||
if (PS_FIXED_ONE_A)
|
||||
if (PS_AA1 != AA1::NONE)
|
||||
{
|
||||
float cov = 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;
|
||||
}
|
||||
else if (PS_FIXED_ONE_A)
|
||||
{
|
||||
// AA (Fixed one) will output a coverage of 1.0 as alpha
|
||||
C.a = 128.0f;
|
||||
}
|
||||
|
||||
bool atst_pass = atst(C);
|
||||
if (PS_AFAIL == AFAIL::KEEP && !atst_pass)
|
||||
discard_fragment();
|
||||
|
||||
float4 alpha_blend = float4(0.f);
|
||||
if (SW_AD_TO_HW)
|
||||
{
|
||||
@@ -1171,10 +1438,7 @@ struct PSMain
|
||||
uint2 denorm_TA = uint2(cb.ta * 255.5f);
|
||||
|
||||
C.rb = (denorm_c.r >> 3) | (((denorm_c.g >> 3) & 0x7) << 5);
|
||||
if (denorm_c.a & 0x80)
|
||||
C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.y & 0x80);
|
||||
else
|
||||
C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80);
|
||||
C.ga = (denorm_c.g >> 6) | ((denorm_c.b >> 3) << 2) | (denorm_TA.x & 0x80);
|
||||
}
|
||||
else if (PS_SHUFFLE_ACROSS)
|
||||
{
|
||||
@@ -1211,18 +1475,28 @@ struct PSMain
|
||||
{
|
||||
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_AFAIL == AFAIL::RGB_ONLY && !atst_pass)
|
||||
out.c0.a = current_color.a;
|
||||
}
|
||||
if (PS_COLOR1)
|
||||
out.c1 = alpha_blend;
|
||||
|
||||
float depth_value = PS_ZFLOOR ? (floor(in.p.z * exp2(32.0f)) * exp2(-32.0f)) : in.p.z;
|
||||
|
||||
|
||||
if (PS_ZCLAMP)
|
||||
out.depth = min(depth_value, cb.max_depth);
|
||||
else if (PS_ZFLOOR)
|
||||
out.depth = depth_value;
|
||||
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.
|
||||
|
||||
if (!atst_pass)
|
||||
{
|
||||
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::RGB_ONLY)
|
||||
out.c0.a = current_color.a;
|
||||
else if (PS_AFAIL == AFAIL::ZB_ONLY)
|
||||
out.c0 = current_color;
|
||||
|
||||
if (PS_AFAIL == AFAIL::RGB_ONLY_SW_Z || PS_AFAIL == AFAIL::FB_ONLY)
|
||||
input_z = current_depth;
|
||||
}
|
||||
|
||||
out.setDepth(input_z);
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -1236,9 +1510,14 @@ fragment float4 fbfetch_test(float4 in [[color(0), raster_order_group(0)]])
|
||||
|
||||
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;
|
||||
#else
|
||||
constant bool NEEDS_RT_TEX = NEEDS_RT;
|
||||
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;
|
||||
|
||||
fragment MainPSOut ps_main(
|
||||
MainPSIn in [[stage_in]],
|
||||
@@ -1249,12 +1528,15 @@ fragment MainPSOut ps_main(
|
||||
#endif
|
||||
#if FBFETCH_SUPPORT
|
||||
float4 rt_fbf [[color(0), raster_order_group(0), function_constant(NEEDS_RT_FBF)]],
|
||||
float ds_fbf [[color(1), raster_order_group(1), function_constant(NEEDS_DS_FBF)]],
|
||||
#endif
|
||||
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> rt [[texture(GSMTLTextureIndexRenderTarget), function_constant(NEEDS_RT_TEX)]],
|
||||
texture2d<float> primidtex [[texture(GSMTLTextureIndexPrimIDs), function_constant(PS_PRIM_CHECKING_READ)]])
|
||||
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)]])
|
||||
{
|
||||
PSMain main(in, cb);
|
||||
main.tex_sampler = s;
|
||||
@@ -1271,12 +1553,24 @@ fragment MainPSOut ps_main(
|
||||
main.prim_id = primid;
|
||||
#endif
|
||||
|
||||
uint2 coord = uint2(in.p.xy);
|
||||
|
||||
if (SW_DEPTH)
|
||||
{
|
||||
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;
|
||||
else
|
||||
main.current_depth = ds_tex.read(coord).x;
|
||||
}
|
||||
|
||||
if (NEEDS_RT)
|
||||
{
|
||||
#if FBFETCH_SUPPORT
|
||||
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(uint2(in.p.xy));
|
||||
main.current_color = HAS_FBFETCH ? rt_fbf : rt.read(coord);
|
||||
#else
|
||||
main.current_color = rt.read(uint2(in.p.xy));
|
||||
main.current_color = rt.read(coord);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
static constexpr u32 g_vs_pc_index = 4;
|
||||
static constexpr u32 g_vs_ib_index = 3;
|
||||
static constexpr u32 g_vs_vb_index = 2;
|
||||
static constexpr u32 g_vs_cb_index = 1;
|
||||
static constexpr u32 g_ps_cb_index = 0;
|
||||
|
||||
@@ -29,6 +32,7 @@ static constexpr u32 VERTEX_BUFFER_SIZE = 32 * 1024 * 1024;
|
||||
static constexpr u32 INDEX_BUFFER_SIZE = 16 * 1024 * 1024;
|
||||
static constexpr u32 VERTEX_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024;
|
||||
static constexpr u32 FRAGMENT_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024;
|
||||
static constexpr u32 VERTEX_PUSH_CONSTANT_BUFFER_SIZE = 1024 * 1024;
|
||||
static constexpr u32 TEXTURE_UPLOAD_BUFFER_SIZE = 128 * 1024 * 1024;
|
||||
|
||||
namespace ReplaceGL
|
||||
@@ -326,10 +330,13 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
|
||||
m_vertex_stream_buffer = GLStreamBuffer::Create(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE);
|
||||
m_index_stream_buffer = GLStreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE);
|
||||
m_expand_index_stream_buffer = GLStreamBuffer::Create(GL_ARRAY_BUFFER, INDEX_BUFFER_SIZE);
|
||||
m_vertex_uniform_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_UNIFORM_BUFFER_SIZE);
|
||||
m_fragment_uniform_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, FRAGMENT_UNIFORM_BUFFER_SIZE);
|
||||
m_vertex_push_constants_stream_buffer = GLStreamBuffer::Create(GL_UNIFORM_BUFFER, VERTEX_PUSH_CONSTANT_BUFFER_SIZE);
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &m_uniform_buffer_alignment);
|
||||
if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_vertex_uniform_stream_buffer || !m_fragment_uniform_stream_buffer)
|
||||
if (!m_vertex_stream_buffer || !m_index_stream_buffer || !m_expand_index_stream_buffer ||
|
||||
!m_vertex_uniform_stream_buffer || !m_fragment_uniform_stream_buffer || !m_vertex_push_constants_stream_buffer)
|
||||
{
|
||||
Host::ReportErrorAsync("GS", "Failed to create vertex/index/uniform streaming buffers");
|
||||
return false;
|
||||
@@ -369,8 +376,16 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
glGenBuffers(1, &m_expand_ibo);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_expand_ibo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, EXPAND_BUFFER_SIZE, expand_data.get(), GL_STATIC_DRAW);
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 2, m_vertex_stream_buffer->GetGLBufferId(), 0, VERTEX_BUFFER_SIZE);
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, g_vs_vb_index, m_vertex_stream_buffer->GetGLBufferId(), 0, VERTEX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (m_features.aa1)
|
||||
{
|
||||
glGenVertexArrays(1, &m_dummy_vao);
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, g_vs_ib_index, m_expand_index_stream_buffer->GetGLBufferId(), 0, INDEX_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
VSSetPushConstants(0, 0, true); // Avoid undefined data.
|
||||
}
|
||||
|
||||
// ****************************************************************
|
||||
@@ -553,6 +568,9 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
|
||||
glDisable(GL_MULTISAMPLE);
|
||||
|
||||
glDisable(GL_DITHER); // Honestly I don't know!
|
||||
|
||||
// Initialise stencil ref/mask.
|
||||
glStencilFunc(GLState::stencil_func, 1, 1);
|
||||
}
|
||||
|
||||
// ****************************************************************
|
||||
@@ -827,38 +845,12 @@ bool GSDeviceOGL::CheckFeatures()
|
||||
m_features.prefer_new_textures = false;
|
||||
m_features.stencil_buffer = true;
|
||||
m_features.test_and_sample_depth = m_features.texture_barrier;
|
||||
if (m_features.texture_barrier)
|
||||
// Auto select chooses depth-as-rt as it appears to be more compatible across hardware.
|
||||
m_features.depth_feedback = GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth;
|
||||
if (!m_features.texture_barrier && m_features.multidraw_fb_copy)
|
||||
{
|
||||
// Auto select chooses depth-as-rt as it appears to be more compatible across hardware.
|
||||
if (GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::DepthAsRT ||
|
||||
GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto)
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::DepthAsRT;
|
||||
}
|
||||
else if (GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth)
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
}
|
||||
}
|
||||
else if (m_features.multidraw_fb_copy)
|
||||
{
|
||||
if (GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth ||
|
||||
GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto)
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::Depth;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_features.depth_feedback = GSDevice::DepthFeedbackSupport::None;
|
||||
// Multidraw fb copy can do depth feedback just fine
|
||||
m_features.depth_feedback |= GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Auto;
|
||||
}
|
||||
|
||||
if (GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
@@ -890,6 +882,8 @@ bool GSDeviceOGL::CheckFeatures()
|
||||
{
|
||||
Console.Warning("GLAD_GL_ARB_conservative_depth is not supported. This will reduce performance.");
|
||||
}
|
||||
|
||||
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -961,14 +955,18 @@ void GSDeviceOGL::DestroyResources()
|
||||
|
||||
m_fragment_uniform_stream_buffer.reset();
|
||||
m_vertex_uniform_stream_buffer.reset();
|
||||
m_vertex_push_constants_stream_buffer.reset();
|
||||
|
||||
glBindVertexArray(0);
|
||||
if (m_expand_ibo != 0)
|
||||
glDeleteVertexArrays(1, &m_expand_ibo);
|
||||
if (m_vao != 0)
|
||||
glDeleteVertexArrays(1, &m_vao);
|
||||
if (m_dummy_vao != 0)
|
||||
glDeleteVertexArrays(1, &m_dummy_vao);
|
||||
|
||||
m_index_stream_buffer.reset();
|
||||
m_expand_index_stream_buffer.reset();
|
||||
m_vertex_stream_buffer.reset();
|
||||
m_texture_upload_buffer.reset();
|
||||
if (m_expand_ibo)
|
||||
@@ -1176,21 +1174,52 @@ void GSDeviceOGL::DrawPrimitive()
|
||||
|
||||
void GSDeviceOGL::DrawIndexedPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
glDrawElementsBaseVertex(m_draw_topology, static_cast<u32>(m_index.count), GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<void*>(static_cast<u32>(m_index.start) * sizeof(u16)), static_cast<GLint>(m_vertex.start));
|
||||
DrawIndexedPrimitive(0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DrawIndexedPrimitive(int offset, int count)
|
||||
{
|
||||
//ASSERT(offset + count <= (int)m_index.count);
|
||||
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
glDrawElementsBaseVertex(m_draw_topology, count, GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<void*>((static_cast<u32>(m_index.start) + static_cast<u32>(offset)) * sizeof(u16)),
|
||||
static_cast<GLint>(m_vertex.start));
|
||||
}
|
||||
|
||||
void GSDeviceOGL::DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion)
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
if (vs_indexing)
|
||||
{
|
||||
VSSetPushConstants(m_vertex.start, m_index.start + offset);
|
||||
glDrawArrays(m_draw_topology, 0, count * vs_indexing_expansion);
|
||||
}
|
||||
else
|
||||
{
|
||||
VSSetPushConstants(m_vertex.start);
|
||||
glDrawElementsBaseVertex(m_draw_topology, count, GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<void*>((static_cast<u32>(m_index.start) + static_cast<u32>(offset)) * sizeof(u16)), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceOGL::Draw(const GSHWDrawConfig& config, int offset, int count)
|
||||
{
|
||||
if (config.vs.expand != GSHWDrawConfig::VSExpand::None)
|
||||
{
|
||||
const bool vs_indexing = config.vs.UseVSExpandIndexBuffer();
|
||||
const u32 vs_indexing_expansion = GetExpansionFactor(config.vs.expand);
|
||||
DrawIndexedPrimitiveVSExpand(offset, count, vs_indexing, vs_indexing_expansion);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawIndexedPrimitive(offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceOGL::Draw(const GSHWDrawConfig& config)
|
||||
{
|
||||
Draw(config, 0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::CommitClear(GSTexture* t, bool use_write_fbo)
|
||||
{
|
||||
GSTextureOGL* T = static_cast<GSTextureOGL*>(t);
|
||||
@@ -1332,15 +1361,6 @@ GLuint GSDeviceOGL::CreateSampler(PSSamplerSelector sel)
|
||||
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
|
||||
const int anisotropy = GSConfig.MaxAnisotropy;
|
||||
if (anisotropy > 1 && sel.aniso)
|
||||
{
|
||||
if (GLAD_GL_ARB_texture_filter_anisotropic)
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY, static_cast<float>(anisotropy));
|
||||
else if (GLAD_GL_EXT_texture_filter_anisotropic)
|
||||
glSamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, static_cast<float>(anisotropy));
|
||||
}
|
||||
|
||||
return sampler;
|
||||
}
|
||||
|
||||
@@ -1435,22 +1455,17 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type
|
||||
header += "#define PS_HAS_CONSERVATIVE_DEPTH 0\n";
|
||||
}
|
||||
|
||||
switch (m_features.depth_feedback)
|
||||
if (!m_features.texture_barrier && !m_features.multidraw_fb_copy)
|
||||
{
|
||||
case GSDevice::DepthFeedbackSupport::None:
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 0\n"; // None
|
||||
static_assert(static_cast<int>(GSDevice::DepthFeedbackSupport::None) == 0);
|
||||
break;
|
||||
case GSDevice::DepthFeedbackSupport::Depth:
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 1\n"; // Depth
|
||||
static_assert(static_cast<int>(GSDevice::DepthFeedbackSupport::Depth) == 1);
|
||||
break;
|
||||
case GSDevice::DepthFeedbackSupport::DepthAsRT:
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 2\n"; // Depth as RT
|
||||
static_assert(static_cast<int>(GSDevice::DepthFeedbackSupport::DepthAsRT) == 2);
|
||||
break;
|
||||
default:
|
||||
pxFail("Incorrect depth feedback support."); // Impossible
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 0\n"; // None
|
||||
}
|
||||
else if (m_features.depth_feedback)
|
||||
{
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 1\n"; // Depth
|
||||
}
|
||||
else
|
||||
{
|
||||
header += "#define DEPTH_FEEDBACK_SUPPORT 2\n"; // Depth as RT
|
||||
}
|
||||
|
||||
// Allow to puts several shader in 1 files
|
||||
@@ -1558,6 +1573,9 @@ std::string GSDeviceOGL::GetPSSource(const PSSelector& sel)
|
||||
+ fmt::format("#define PS_NO_COLOR {}\n", sel.no_color)
|
||||
+ fmt::format("#define PS_NO_COLOR1 {}\n", sel.no_color1)
|
||||
+ fmt::format("#define PS_ZTST {}\n", sel.ztst)
|
||||
+ fmt::format("#define PS_AA1 {}\n", static_cast<u32>(sel.aa1))
|
||||
+ fmt::format("#define PS_ABE {}\n", sel.abe)
|
||||
+ fmt::format("#define PS_ANISOTROPIC_FILTERING {}", sel.sw_aniso)
|
||||
;
|
||||
|
||||
std::string src = GenGlslHeader("ps_main", GL_FRAGMENT_SHADER, macro);
|
||||
@@ -2115,6 +2133,40 @@ void GSDeviceOGL::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GS
|
||||
DrawPrimitive();
|
||||
}
|
||||
|
||||
__fi static void WriteToStreamBuffer(GLStreamBuffer* sb, u32 index, u32 align, const void* data, u32 size)
|
||||
{
|
||||
const auto res = sb->Map(align, size);
|
||||
std::memcpy(res.pointer, data, size);
|
||||
sb->Unmap(size);
|
||||
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, index, sb->GetGLBufferId(), res.buffer_offset, size);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::VSSetUniformBuffer(GSHWDrawConfig::VSConstantBuffer& cb)
|
||||
{
|
||||
WriteToStreamBuffer(m_vertex_uniform_stream_buffer.get(), g_vs_cb_index,
|
||||
m_uniform_buffer_alignment, &cb, sizeof(cb));
|
||||
}
|
||||
|
||||
void GSDeviceOGL::PSSetUniformBuffer(GSHWDrawConfig::PSConstantBuffer& cb)
|
||||
{
|
||||
WriteToStreamBuffer(m_fragment_uniform_stream_buffer.get(), g_ps_cb_index,
|
||||
m_uniform_buffer_alignment, &cb, sizeof(cb));
|
||||
}
|
||||
|
||||
void GSDeviceOGL::VSSetPushConstants(u32 base_vertex, u32 base_index, bool force_update)
|
||||
{
|
||||
GSHWDrawConfig::VSPushConstants vs_pc;
|
||||
vs_pc.base_vertex = base_vertex;
|
||||
vs_pc.base_index = base_index;
|
||||
|
||||
if (m_vs_pc_cache.Update(vs_pc) || force_update)
|
||||
{
|
||||
WriteToStreamBuffer(m_vertex_push_constants_stream_buffer.get(), g_vs_pc_index,
|
||||
m_uniform_buffer_alignment, &vs_pc, sizeof(vs_pc));
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceOGL::IASetVAO(GLuint vao)
|
||||
{
|
||||
if (GLState::vao == vao)
|
||||
@@ -2134,14 +2186,24 @@ void GSDeviceOGL::IASetVertexBuffer(const void* vertices, size_t count, size_t a
|
||||
m_vertex_stream_buffer->Unmap(size);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::IASetIndexBuffer(const void* index, size_t count)
|
||||
void GSDeviceOGL::SetIndexBuffer(std::unique_ptr<GLStreamBuffer>& buffer, const void* index, size_t count)
|
||||
{
|
||||
const u32 size = static_cast<u32>(count) * sizeof(u16);
|
||||
auto res = m_index_stream_buffer->Map(sizeof(u16), size);
|
||||
auto res = buffer->Map(sizeof(u16), size);
|
||||
m_index.start = res.index_aligned;
|
||||
m_index.count = count;
|
||||
std::memcpy(res.pointer, index, size);
|
||||
m_index_stream_buffer->Unmap(size);
|
||||
buffer->Unmap(size);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
SetIndexBuffer(m_index_stream_buffer, index, count);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::VSSetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
SetIndexBuffer(m_expand_index_stream_buffer, index, count);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::IASetPrimitiveTopology(GLenum topology)
|
||||
@@ -2153,7 +2215,7 @@ void GSDeviceOGL::PSSetShaderResource(int i, GSTexture* sr)
|
||||
{
|
||||
pxAssert(i < static_cast<int>(std::size(GLState::tex_unit)));
|
||||
|
||||
const GLuint id = static_cast<GSTextureOGL*>(sr)->GetID();
|
||||
const GLuint id = sr ? static_cast<GSTextureOGL*>(sr)->GetID() : 0;
|
||||
if (GLState::tex_unit[i] != id)
|
||||
{
|
||||
GLState::tex_unit[i] = id;
|
||||
@@ -2562,15 +2624,6 @@ void GSDeviceOGL::SetScissor(const GSVector4i& scissor)
|
||||
}
|
||||
}
|
||||
|
||||
__fi static void WriteToStreamBuffer(GLStreamBuffer* sb, u32 index, u32 align, const void* data, u32 size)
|
||||
{
|
||||
const auto res = sb->Map(align, size);
|
||||
std::memcpy(res.pointer, data, size);
|
||||
sb->Unmap(size);
|
||||
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, index, sb->GetGLBufferId(), res.buffer_offset, size);
|
||||
}
|
||||
|
||||
void GSDeviceOGL::SetupPipeline(const ProgramSelector& psel)
|
||||
{
|
||||
auto it = m_programs.find(psel);
|
||||
@@ -2624,7 +2677,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
GLState::scissor = config.scissor;
|
||||
}
|
||||
|
||||
if (config.tex)
|
||||
if (config.tex && (m_features.texture_barrier || (config.tex != config.rt)))
|
||||
CommitClear(config.tex, true);
|
||||
if (config.pal)
|
||||
CommitClear(config.pal, true);
|
||||
@@ -2690,7 +2743,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// Destination Alpha Setup
|
||||
const bool multidraw_fb_copy = m_features.multidraw_fb_copy && (config.require_one_barrier || config.require_full_barrier);
|
||||
const bool need_barrier = config.require_one_barrier || (config.require_full_barrier && m_features.feedback_loops());
|
||||
switch (config.destination_alpha)
|
||||
{
|
||||
case GSHWDrawConfig::DestinationAlphaMode::Off:
|
||||
@@ -2705,7 +2758,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
break;
|
||||
case GSHWDrawConfig::DestinationAlphaMode::StencilOne:
|
||||
if (m_features.texture_barrier || multidraw_fb_copy)
|
||||
if (need_barrier)
|
||||
{
|
||||
// Cleared after RT bind.
|
||||
break;
|
||||
@@ -2717,14 +2770,18 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
IASetVertexBuffer(config.verts, config.nverts, GetVertexAlignment(config.vs.expand));
|
||||
m_vertex.start *= GetExpansionFactor(config.vs.expand);
|
||||
|
||||
if (config.vs.UseExpandIndexBuffer())
|
||||
if (config.vs.UseFixedExpandIndexBuffer())
|
||||
{
|
||||
IASetVAO(m_expand_vao);
|
||||
m_index.start = 0;
|
||||
m_index.count = config.nindices;
|
||||
}
|
||||
else if (config.vs.UseVSExpandIndexBuffer())
|
||||
{
|
||||
IASetVAO(m_dummy_vao); // Unbind vertex buffer from IA to prevent unwanted fetches.
|
||||
VSSetIndexBuffer(config.indices, config.nindices);
|
||||
}
|
||||
else
|
||||
{
|
||||
IASetVAO(m_vao);
|
||||
@@ -2740,28 +2797,21 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
IASetPrimitiveTopology(topology);
|
||||
|
||||
if (config.tex)
|
||||
if (config.tex && (m_features.texture_barrier || (config.tex != config.rt)))
|
||||
PSSetShaderResource(0, config.tex);
|
||||
if (config.pal)
|
||||
PSSetShaderResource(1, config.pal);
|
||||
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier))
|
||||
PSSetShaderResource(2, colclip_rt ? colclip_rt : config.rt);
|
||||
const bool depth_feedback = m_features.depth_feedback == GSDevice::DepthFeedbackSupport::Depth;
|
||||
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier) && config.ps.IsFeedbackLoopDepth() &&
|
||||
(depth_feedback || m_features.depth_feedback == GSDevice::DepthFeedbackSupport::DepthAsRT))
|
||||
PSSetShaderResource(4, depth_feedback ? config.ds : config.ds_as_rt);
|
||||
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier) && config.ps.IsFeedbackLoopDepth())
|
||||
PSSetShaderResource(4, m_features.depth_feedback ? config.ds : m_ds_as_rt);
|
||||
|
||||
SetupSampler(config.sampler);
|
||||
|
||||
if (m_vs_cb_cache.Update(config.cb_vs))
|
||||
{
|
||||
WriteToStreamBuffer(m_vertex_uniform_stream_buffer.get(), g_vs_cb_index,
|
||||
m_uniform_buffer_alignment, &config.cb_vs, sizeof(config.cb_vs));
|
||||
}
|
||||
VSSetUniformBuffer(m_vs_cb_cache);
|
||||
if (m_ps_cb_cache.Update(config.cb_ps))
|
||||
{
|
||||
WriteToStreamBuffer(m_fragment_uniform_stream_buffer.get(), g_ps_cb_index,
|
||||
m_uniform_buffer_alignment, &config.cb_ps, sizeof(config.cb_ps));
|
||||
}
|
||||
PSSetUniformBuffer(m_ps_cb_cache);
|
||||
|
||||
ProgramSelector psel;
|
||||
psel.vs = config.vs;
|
||||
@@ -2771,7 +2821,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
|
||||
SetupPipeline(psel);
|
||||
|
||||
bool rt_hazard_barrier = config.tex && (config.tex == config.ds || config.tex == config.rt || config.tex == config.ds_as_rt);
|
||||
bool rt_hazard_barrier = config.tex && (config.tex == config.ds || config.tex == config.rt);
|
||||
// In Time Crisis:
|
||||
// 1. Fullscreen sprite reads depth and writes alpha (rt_hazard_barrier true from config.ds == config.tex)
|
||||
// 2. Fullscreen sprite writes gray, rta hw blend blends based on dst alpha.
|
||||
@@ -2821,7 +2871,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
SetupOM(dss);
|
||||
|
||||
// Compute primitiveID max that pass the date test (Draw without barrier)
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
|
||||
psel.ps.date = 3;
|
||||
config.alpha_second_pass.ps.date = 3;
|
||||
@@ -2829,7 +2879,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
PSSetShaderResource(3, primid_texture);
|
||||
}
|
||||
|
||||
if (config.ds_as_rt)
|
||||
if (m_ds_as_rt)
|
||||
{
|
||||
// We must clear the blend equation of any dual source blending factors or
|
||||
// it may interact badly with MRTs, even if blending is disabled.
|
||||
@@ -2849,16 +2899,13 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
GSTexture* draw_rt = colclip_rt ? colclip_rt : config.rt;
|
||||
GSTexture* draw_ds_as_rt = config.ds_as_rt;
|
||||
GSTexture* draw_ds_as_rt = m_ds_as_rt;
|
||||
GSTexture* draw_ds = config.ds;
|
||||
|
||||
// Clear texture binding when it's bound to RT or DS.
|
||||
if (!config.tex && ((draw_rt && static_cast<GSTextureOGL*>(draw_rt)->GetID() == GLState::tex_unit[0]) ||
|
||||
(draw_ds && static_cast<GSTextureOGL*>(draw_ds)->GetID() == GLState::tex_unit[0])))
|
||||
{
|
||||
GLState::tex_unit[0] = 0;
|
||||
glBindTextureUnit(0, 0);
|
||||
}
|
||||
PSSetShaderResource(0, nullptr);
|
||||
|
||||
// Avoid changing framebuffer just to switch from rt+depth to rt and vice versa.
|
||||
bool fb_optimization_needs_barrier = false;
|
||||
@@ -2884,20 +2931,30 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
glTextureBarrier();
|
||||
}
|
||||
|
||||
if (draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy) || (config.tex && config.tex == config.rt)) &&
|
||||
!m_features.texture_barrier)
|
||||
const bool tex_is_fb = config.tex && config.tex == draw_rt;
|
||||
const bool rt_feedbackloop_pass1 = config.ps.IsFeedbackLoopRT() || tex_is_fb;
|
||||
const bool rt_feedbackloop_pass2 = config.alpha_second_pass.ps.IsFeedbackLoopRT() || tex_is_fb;
|
||||
if (draw_rt && !m_features.texture_barrier && (((config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
|
||||
(rt_feedbackloop_pass1 || rt_feedbackloop_pass2))))
|
||||
{
|
||||
config.require_one_barrier |= (tex_is_fb && !config.require_full_barrier);
|
||||
config.alpha_second_pass.require_one_barrier |= (tex_is_fb && !config.require_full_barrier);
|
||||
|
||||
// Requires a copy of the RT.
|
||||
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true);
|
||||
|
||||
if (!draw_rt_clone)
|
||||
Console.Warning("GL: Failed to allocate temp texture for RT copy.");
|
||||
}
|
||||
|
||||
if (draw_ds && (config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) &&
|
||||
!m_features.texture_barrier && depth_feedback && config.ps.IsFeedbackLoopDepth())
|
||||
const bool ds_feedbackloop_pass1 = config.ps.IsFeedbackLoopDepth();
|
||||
const bool ds_feedbackloop_pass2 = config.alpha_second_pass.ps.IsFeedbackLoopDepth();
|
||||
if (draw_ds && !m_features.texture_barrier && m_features.depth_feedback &&
|
||||
(config.require_one_barrier || (config.require_full_barrier && m_features.multidraw_fb_copy)) && (ds_feedbackloop_pass1 || ds_feedbackloop_pass2))
|
||||
{
|
||||
// Requires a copy of the DS.
|
||||
draw_ds_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_ds->GetFormat(), true);
|
||||
|
||||
if (!draw_ds_clone)
|
||||
Console.Warning("GL: Failed to allocate temp texture for DS copy.");
|
||||
}
|
||||
@@ -2907,13 +2964,13 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
SetupOM(config.depth);
|
||||
|
||||
// Clear stencil as close as possible to the RT bind, to avoid framebuffer swaps.
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && (m_features.texture_barrier || multidraw_fb_copy))
|
||||
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne && need_barrier)
|
||||
{
|
||||
constexpr GLint clear_color = 1;
|
||||
glClearBufferiv(GL_STENCIL, 0, &clear_color);
|
||||
}
|
||||
|
||||
SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds_clone, draw_ds,
|
||||
SendHWDraw(config, rt_feedbackloop_pass1 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass1 ? draw_ds_clone : nullptr, draw_ds,
|
||||
config.require_one_barrier, config.require_full_barrier);
|
||||
|
||||
if (config.blend_multi_pass.enable)
|
||||
@@ -2933,7 +2990,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
psel.ps.blend_hw = config.blend_multi_pass.blend_hw;
|
||||
psel.ps.dither = config.blend_multi_pass.dither;
|
||||
SetupPipeline(psel);
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
if (config.alpha_second_pass.enable)
|
||||
@@ -2942,8 +2999,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
if (config.cb_ps.FogColor_AREF.a != config.alpha_second_pass.ps_aref)
|
||||
{
|
||||
config.cb_ps.FogColor_AREF.a = config.alpha_second_pass.ps_aref;
|
||||
WriteToStreamBuffer(m_fragment_uniform_stream_buffer.get(), g_ps_cb_index,
|
||||
m_uniform_buffer_alignment, &config.cb_ps, sizeof(config.cb_ps));
|
||||
PSSetUniformBuffer(config.cb_ps);
|
||||
}
|
||||
|
||||
psel.ps = config.alpha_second_pass.ps;
|
||||
@@ -2960,9 +3016,10 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
{
|
||||
OMSetBlendState();
|
||||
}
|
||||
const bool one_barrier = config.alpha_second_pass.require_one_barrier && m_features.feedback_loops();
|
||||
SetupOM(config.alpha_second_pass.depth);
|
||||
SendHWDraw(config, draw_rt_clone, draw_rt, draw_ds_clone, draw_ds,
|
||||
m_features.texture_barrier ? config.alpha_second_pass.require_one_barrier : false, config.alpha_second_pass.require_full_barrier);
|
||||
SendHWDraw(config, rt_feedbackloop_pass2 ? draw_rt_clone : nullptr, draw_rt, ds_feedbackloop_pass2 ? draw_ds_clone : nullptr, draw_ds,
|
||||
one_barrier, config.alpha_second_pass.require_full_barrier);
|
||||
}
|
||||
|
||||
if (colclip_rt)
|
||||
@@ -3026,14 +3083,16 @@ void GSDeviceOGL::SendHWDraw(const GSHWDrawConfig& config,
|
||||
const u32 count = (*config.drawlist)[n] * indices_per_prim;
|
||||
|
||||
if (m_features.texture_barrier)
|
||||
{
|
||||
glTextureBarrier();
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSVector4i original_bbox = (*config.drawlist_bbox)[n].rintersect(config.drawarea);
|
||||
CopyAndBind(ProcessCopyArea(rtsize, original_bbox));
|
||||
}
|
||||
|
||||
DrawIndexedPrimitive(p, count);
|
||||
Draw(config, p, count);
|
||||
p += count;
|
||||
}
|
||||
|
||||
@@ -3054,7 +3113,7 @@ void GSDeviceOGL::SendHWDraw(const GSHWDrawConfig& config,
|
||||
}
|
||||
}
|
||||
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
// Note: used as a callback of DebugMessageCallback. Don't change the signature
|
||||
|
||||
@@ -157,13 +157,16 @@ private:
|
||||
|
||||
std::unique_ptr<GLStreamBuffer> m_vertex_stream_buffer;
|
||||
std::unique_ptr<GLStreamBuffer> m_index_stream_buffer;
|
||||
std::unique_ptr<GLStreamBuffer> m_expand_index_stream_buffer;
|
||||
GLuint m_expand_ibo = 0;
|
||||
GLuint m_vao = 0;
|
||||
GLuint m_expand_vao = 0;
|
||||
GLuint m_dummy_vao = 0;
|
||||
GLenum m_draw_topology = 0;
|
||||
|
||||
std::unique_ptr<GLStreamBuffer> m_vertex_uniform_stream_buffer;
|
||||
std::unique_ptr<GLStreamBuffer> m_fragment_uniform_stream_buffer;
|
||||
std::unique_ptr<GLStreamBuffer> m_vertex_push_constants_stream_buffer;
|
||||
GLint m_uniform_buffer_alignment = 0;
|
||||
|
||||
struct
|
||||
@@ -233,6 +236,7 @@ private:
|
||||
|
||||
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
|
||||
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
|
||||
GSHWDrawConfig::VSPushConstants m_vs_pc_cache;
|
||||
|
||||
std::string m_shader_tfx_vgs;
|
||||
std::string m_shader_tfx_fs;
|
||||
@@ -272,6 +276,8 @@ private:
|
||||
|
||||
void DrawStretchRect(const GSVector4& sRect, const GSVector4& dRect, const GSVector2i& ds);
|
||||
|
||||
void SetIndexBuffer(std::unique_ptr<GLStreamBuffer>& buffer, const void* index, size_t count);
|
||||
|
||||
protected:
|
||||
virtual void DoStretchRect(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect,
|
||||
GSHWDrawConfig::ColorMaskSelector cms, ShaderConvert shader, bool linear) override;
|
||||
@@ -312,9 +318,15 @@ public:
|
||||
bool SetGPUTimingEnabled(bool enabled) override;
|
||||
float GetAndResetAccumulatedGPUTime() override;
|
||||
|
||||
// Helpers and utility draws.
|
||||
void DrawPrimitive();
|
||||
void DrawIndexedPrimitive();
|
||||
void DrawIndexedPrimitive(int offset, int count);
|
||||
void DrawIndexedPrimitiveVSExpand(int offset, int count, bool vs_indexing, int vs_indexing_expansion);
|
||||
|
||||
// Main GS primitive draws.
|
||||
void Draw(const GSHWDrawConfig& config);
|
||||
void Draw(const GSHWDrawConfig& config, int offset, int count);
|
||||
|
||||
std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override;
|
||||
|
||||
@@ -345,10 +357,15 @@ public:
|
||||
const bool one_barrier, const bool full_barrier);
|
||||
void SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox);
|
||||
|
||||
void VSSetUniformBuffer(GSHWDrawConfig::VSConstantBuffer& cb);
|
||||
void PSSetUniformBuffer(GSHWDrawConfig::PSConstantBuffer& cb);
|
||||
void VSSetPushConstants(u32 base_vertex, u32 base_index = 0, bool force_update = false);
|
||||
|
||||
void IASetVAO(GLuint vao);
|
||||
void IASetPrimitiveTopology(GLenum topology);
|
||||
void IASetVertexBuffer(const void* vertices, size_t count, size_t align_multiplier = 1);
|
||||
void IASetIndexBuffer(const void* index, size_t count);
|
||||
void VSSetIndexBuffer(const void* index, size_t count);
|
||||
|
||||
void PSSetShaderResource(int i, GSTexture* sr);
|
||||
void PSSetSamplerState(GLuint ss);
|
||||
|
||||
@@ -77,8 +77,10 @@ static const auto& _d4_f = v9;
|
||||
static const auto& _test = v8;
|
||||
static const auto& _fd = v2;
|
||||
|
||||
#define _local(field) MemOperand(_locals, offsetof(GSScanlineLocalData, field))
|
||||
#define _global(field) MemOperand(_globals, offsetof(GSScanlineGlobalData, field))
|
||||
// Yay, you can't offsetof with non-constant array indices in GCC
|
||||
#define OFFSETOF(base, field) (reinterpret_cast<uptr>(&reinterpret_cast<base*>(0)->field))
|
||||
#define _local(field) MemOperand(_locals, OFFSETOF(GSScanlineLocalData, field))
|
||||
#define _global(field) MemOperand(_globals, OFFSETOF(GSScanlineGlobalData, field))
|
||||
#define armAsm (&m_emitter)
|
||||
|
||||
GSDrawScanlineCodeGenerator::GSDrawScanlineCodeGenerator(u64 key, void* code, size_t maxsize)
|
||||
|
||||
@@ -527,6 +527,10 @@ void GSRasterizer::DrawEdgeLine(const GSVertexSW& v0, const GSVertexSW& v1, cons
|
||||
const int rxi1 = static_cast<int>(rx1);
|
||||
const int ryi1 = static_cast<int>(ry1);
|
||||
|
||||
// Early exit for horizontal lines.
|
||||
if (delta_y == 0.0f && !IsOneOfMyScanlines(ryi0) && !aa)
|
||||
return;
|
||||
|
||||
const GSVertexSW dedge = dv / GSVector4(std::abs(step_x ? delta_x : delta_y));
|
||||
|
||||
GSVertexSW edge(v0);
|
||||
@@ -1546,7 +1550,7 @@ void GSRasterizerList::Queue(const GSRingHeap::SharedPtr<GSRasterizerData>& data
|
||||
m_ds.SetupDraw(*data.get());
|
||||
}
|
||||
|
||||
pxAssert(r.top >= 0 && r.top < 2048 && r.bottom >= 0 && r.bottom < 2048);
|
||||
pxAssert(r.top >= 0 && r.top <= 2048 && r.bottom >= 0 && r.bottom <= 2048);
|
||||
|
||||
int top = r.top >> m_thread_height;
|
||||
int bottom = std::min<int>((r.bottom + (1 << m_thread_height) - 1) >> m_thread_height, top + (int)m_workers.size());
|
||||
|
||||
@@ -320,14 +320,14 @@ void GSRendererSW::RewriteVerticesIfSTOverflow()
|
||||
constexpr int n = GSUtil::GetClassVertexCount(primclass);
|
||||
|
||||
// Make sure the copy buffer is large enough.
|
||||
while (m_vertex.maxcount < m_index.tail)
|
||||
while (m_vertex->maxcount < m_index->tail)
|
||||
GrowVertexBuffer();
|
||||
|
||||
GSVertex* RESTRICT vertex = m_vertex.buff;
|
||||
GSVertex* RESTRICT vertex_copy = m_vertex.buff_copy;
|
||||
u16* RESTRICT index = m_index.buff;
|
||||
GSVertex* RESTRICT vertex = m_vertex->buff;
|
||||
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
|
||||
u16* RESTRICT index = m_index->buff;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(m_index.tail); i += n)
|
||||
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
|
||||
{
|
||||
GSVector4 stcq[n];
|
||||
|
||||
@@ -381,18 +381,18 @@ void GSRendererSW::RewriteVerticesIfSTOverflow()
|
||||
}
|
||||
|
||||
// Swap the buffers and fix the counts.
|
||||
std::swap(m_vertex.buff, m_vertex.buff_copy);
|
||||
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;
|
||||
std::swap(m_vertex->buff, m_vertex->buff_copy);
|
||||
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
|
||||
|
||||
// Recalculate ST min/max/eq in the vertex trace.
|
||||
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
|
||||
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
|
||||
for (int i = 0; i < static_cast<int>(m_index.tail); i += n)
|
||||
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
|
||||
{
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex.buff[i + j].m[0]));
|
||||
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex.buff[i + 1].RGBAQ.Q;
|
||||
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
|
||||
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex->buff[i + 1].RGBAQ.Q;
|
||||
stcq = (stcq / Q).xyzw(stcq);
|
||||
|
||||
tmin = tmin.min(stcq);
|
||||
@@ -451,11 +451,11 @@ void GSRendererSW::Draw()
|
||||
SharedData* sd = static_cast<SharedData*>(data.get());
|
||||
|
||||
sd->primclass = m_vt.m_primclass;
|
||||
sd->buff = (u8*)m_vertex_heap.alloc(sizeof(GSVertexSW) * ((m_vertex.next + 1) & ~1) + sizeof(u32) * m_index.tail, 64);
|
||||
sd->buff = (u8*)m_vertex_heap.alloc(sizeof(GSVertexSW) * ((m_vertex->next + 1) & ~1) + sizeof(u32) * m_index->tail, 64);
|
||||
sd->vertex = (GSVertexSW*)sd->buff;
|
||||
sd->vertex_count = m_vertex.next;
|
||||
sd->index = (u16*)(sd->buff + sizeof(GSVertexSW) * ((m_vertex.next + 1) & ~1));
|
||||
sd->index_count = m_index.tail;
|
||||
sd->vertex_count = m_vertex->next;
|
||||
sd->index = (u16*)(sd->buff + sizeof(GSVertexSW) * ((m_vertex->next + 1) & ~1));
|
||||
sd->index_count = m_index->tail;
|
||||
sd->scanmsk_value = m_draw_env->SCANMSK.MSK;
|
||||
|
||||
// skip per pixel division if q is constant.
|
||||
@@ -463,12 +463,30 @@ void GSRendererSW::Draw()
|
||||
// If you have both GS_SPRITE_CLASS && m_vt.m_eq.q, it will depends on the first part of the 'OR'
|
||||
u32 q_div = !IsMipMapActive() && ((m_vt.m_eq.q && m_vt.m_min.t.z != 1.0f) || (!m_vt.m_eq.q && m_vt.m_primclass == GS_SPRITE_CLASS));
|
||||
|
||||
GSVertexSW::s_cvb[m_vt.m_primclass][PRIM->TME][PRIM->FST][q_div](m_context, sd->vertex, m_vertex.buff, m_vertex.next);
|
||||
GSVertexSW::s_cvb[m_vt.m_primclass][PRIM->TME][PRIM->FST][q_div](m_context, sd->vertex, m_vertex->buff, m_vertex->next);
|
||||
|
||||
std::memcpy(sd->index, m_index.buff, sizeof(u16) * m_index.tail);
|
||||
std::memcpy(sd->index, m_index->buff, sizeof(u16) * m_index->tail);
|
||||
|
||||
GSVector4i scissor = context->scissor.in;
|
||||
GSVector4i bbox = GSVector4i(m_vt.m_min.p.floor().upld(m_vt.m_max.p.floor())) + GSVector4i(0, 0, 1, 1); // right/bottom should be exclusive so +1
|
||||
GSVector4i bbox;
|
||||
|
||||
if (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_POINT_CLASS)
|
||||
{
|
||||
// min: round, max: round
|
||||
bbox = GSVector4i((m_vt.m_min.p + GSVector4(0.5f)).floor().upld((m_vt.m_max.p + GSVector4(0.5f)).floor()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// min: ceil, max: floor
|
||||
bbox = GSVector4i(m_vt.m_min.p.ceil().upld(m_vt.m_max.p.floor()));
|
||||
}
|
||||
|
||||
if (PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS))
|
||||
{
|
||||
bbox += GSVector4i(-1, -1, 1, 1); // Expand bbox by 1 on all sides when using antialiasing.
|
||||
}
|
||||
|
||||
bbox += GSVector4i(0, 0, 1, 1); // right/bottom should be exclusive so +1
|
||||
|
||||
GSVector4i r = bbox.rintersect(scissor);
|
||||
|
||||
@@ -485,12 +503,12 @@ void GSRendererSW::Draw()
|
||||
{
|
||||
int n = GSUtil::GetVertexCount(PRIM->PRIM);
|
||||
|
||||
for (u32 i = 0, j = 0; i < m_index.tail; i += n, j++)
|
||||
for (u32 i = 0, j = 0; i < m_index->tail; i += n, j++)
|
||||
{
|
||||
for (int k = 0; k < n; k++)
|
||||
{
|
||||
GSVertex* v = &m_vertex.buff[m_index.buff[i + k]];
|
||||
GSVertex* vn = &m_vertex.buff[m_index.buff[i + n - 1]];
|
||||
GSVertex* v = &m_vertex->buff[m_index->buff[i + k]];
|
||||
GSVertex* vn = &m_vertex->buff[m_index->buff[i + n - 1]];
|
||||
|
||||
fprintf(s_fp, "%d:%d %f %f %f %f\n",
|
||||
j, k,
|
||||
@@ -1393,7 +1411,7 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
|
||||
gd.sel.pabe = 1;
|
||||
}
|
||||
|
||||
if (PRIM->AA1 && (primclass == GS_LINE_CLASS || primclass == GS_TRIANGLE_CLASS))
|
||||
if (IsCoverageAlpha())
|
||||
{
|
||||
gd.sel.aa1 = 1;
|
||||
}
|
||||
@@ -1505,12 +1523,12 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
|
||||
|
||||
u32 ofx = context->XYOFFSET.OFX;
|
||||
|
||||
for (int i = 0, j = m_vertex.tail; i < j; i++)
|
||||
for (int i = 0, j = m_vertex->tail; i < j; i++)
|
||||
{
|
||||
#if _M_SSE >= 0x501
|
||||
if ((((m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
|
||||
if ((((m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
|
||||
#else
|
||||
if ((((m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
|
||||
if ((((m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
|
||||
#endif
|
||||
{
|
||||
gd.sel.notest = 0;
|
||||
@@ -1523,6 +1541,11 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSRendererSW::IsCoverageAlphaSupported()
|
||||
{
|
||||
return IsCoverageAlpha();
|
||||
}
|
||||
|
||||
GSRendererSW::SharedData::SharedData()
|
||||
: m_fpsm(0)
|
||||
, m_zpsm(0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user