mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-18 21:15:42 +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 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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"
|
||||
@@ -31954,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"
|
||||
@@ -32128,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"
|
||||
@@ -44023,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 アディダス"
|
||||
@@ -54490,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]"
|
||||
@@ -69655,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:
|
||||
@@ -69665,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"
|
||||
@@ -70098,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"
|
||||
@@ -75575,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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -120,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);
|
||||
|
||||
@@ -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>
|
||||
@@ -423,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="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::TextBrowserInteraction</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>viewProfile</cstring>
|
||||
</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::TextInteractionFlag::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">
|
||||
@@ -528,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>
|
||||
@@ -556,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);
|
||||
|
||||
@@ -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);
|
||||
@@ -492,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> "
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+3065
-2888
File diff suppressed because it is too large
Load Diff
+24
-1
@@ -1540,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
|
||||
@@ -1553,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.
|
||||
@@ -3721,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)
|
||||
|
||||
@@ -792,6 +792,7 @@ struct Pcsx2Config
|
||||
Mipmap : 1,
|
||||
HWMipmap : 1,
|
||||
HWAccurateAlphaTest: 1,
|
||||
HWAA1 : 1,
|
||||
ManualUserHacks : 1,
|
||||
UserHacks_AlignSpriteX : 1,
|
||||
UserHacks_CPUFBConversion : 1,
|
||||
@@ -1402,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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
+218
-170
@@ -1279,12 +1279,12 @@ void GSState::DumpTransferList(const std::string& filename)
|
||||
(*file) << std::endl;
|
||||
|
||||
// clear, EE->GS, or GS->GS
|
||||
(*file) << LIST_ITEM << "type: " << (transfer.zero_clear ? "clear" : ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) ? "EE_to_GS" : "GS_to_GS")) << std::endl;
|
||||
(*file) << LIST_ITEM << "type: " << ((transfer.transfer_type == EEGS_TransferType::Clear) ? "clear" : ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) ? "EE_to_GS" : "GS_to_GS")) << std::endl;
|
||||
|
||||
// Dump BITBLTBUF
|
||||
(*file) << INDENT << "BITBLTBUF: " << OPEN_MAP;
|
||||
|
||||
const bool gs_to_gs = (transfer.transfer_type == EEGS_TransferType::GS_to_GS) && !transfer.zero_clear;
|
||||
const bool gs_to_gs = (transfer.transfer_type == EEGS_TransferType::GS_to_GS) && transfer.transfer_type != EEGS_TransferType::Clear;
|
||||
|
||||
if (gs_to_gs)
|
||||
{
|
||||
@@ -1330,11 +1330,11 @@ void GSState::DumpTransferImages()
|
||||
const GSUploadQueue& transfer = m_draw_transfers[i];
|
||||
|
||||
std::string filename;
|
||||
if ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) || transfer.zero_clear)
|
||||
if ((transfer.transfer_type == EEGS_TransferType::EE_to_GS) || transfer.transfer_type == EEGS_TransferType::Clear)
|
||||
{
|
||||
// clear or EE->GS: only the destination info is relevant.
|
||||
filename = GetDrawDumpPath("%05lld_transfer%02d_%s_%04x_%d_%s_%d_%d_%d_%d.png",
|
||||
s_n, transfer_n++, (transfer.zero_clear ? "clear" : "EE_to_GS"), transfer.blit.DBP, transfer.blit.DBW,
|
||||
s_n, transfer_n++, ((transfer.transfer_type == EEGS_TransferType::Clear) ? "clear" : "EE_to_GS"), transfer.blit.DBP, transfer.blit.DBW,
|
||||
GSUtil::GetPSMName(transfer.blit.DPSM), transfer.rect.x, transfer.rect.y, transfer.rect.z, transfer.rect.w);
|
||||
}
|
||||
else
|
||||
@@ -2403,7 +2403,7 @@ void GSState::FlushWrite()
|
||||
|
||||
if (m_draw_transfers.size() > 0 && m_tr.m_blit.DBP == m_draw_transfers.back().blit.DBP)
|
||||
{
|
||||
m_draw_transfers.back().rect = r;
|
||||
m_draw_transfers.back().rect = m_draw_transfers.back().rect.runion(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2666,7 +2666,6 @@ void GSState::FlushPrim()
|
||||
GSVector4i* RESTRICT vert_ptr = (GSVector4i*)&m_vertex->buff[i];
|
||||
GSVector4i v = vert_ptr[1];
|
||||
v = v.xxxx().u16to32().sub32(m_xyof);
|
||||
v = v.blend32<12>(v.sra32<4>());
|
||||
m_vertex->xy[i & 3] = v;
|
||||
m_vertex->xy_tail = unused;
|
||||
}
|
||||
@@ -2912,12 +2911,11 @@ void GSState::Write(const u8* mem, int len)
|
||||
m_draw_transfers.pop_back();
|
||||
transfer.rect = transfer.rect.runion(r);
|
||||
transfer.draw = s_n;
|
||||
transfer.zero_clear = false;
|
||||
m_draw_transfers.push_back(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSUploadQueue new_transfer = {blit, r, s_n, false, EEGS_TransferType::EE_to_GS};
|
||||
const GSUploadQueue new_transfer = {blit, s_n, r, EEGS_TransferType::EE_to_GS};
|
||||
m_draw_transfers.push_back(new_transfer);
|
||||
}
|
||||
|
||||
@@ -3112,12 +3110,11 @@ void GSState::Move()
|
||||
m_draw_transfers.pop_back();
|
||||
transfer.rect = transfer.rect.runion(r);
|
||||
transfer.draw = s_n;
|
||||
transfer.zero_clear = false;
|
||||
m_draw_transfers.push_back(transfer);
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, r, s_n, false, EEGS_TransferType::GS_to_GS};
|
||||
const GSUploadQueue new_transfer = {m_env.BITBLTBUF, s_n, r, EEGS_TransferType::GS_to_GS};
|
||||
m_draw_transfers.push_back(new_transfer);
|
||||
}
|
||||
|
||||
@@ -3790,10 +3787,8 @@ void GSState::UpdateContext()
|
||||
|
||||
void GSState::UpdateScissor()
|
||||
{
|
||||
m_scissor_cull_min = m_context->scissor.cull.xyxy();
|
||||
m_scissor_cull_max = m_context->scissor.cull.zwzw();
|
||||
m_xyof = m_context->scissor.xyof;
|
||||
m_scissor_invalid = !m_context->scissor.in.gt32(m_context->scissor.in.zwzw()).allfalse();
|
||||
m_scissor_invalid = m_context->scissor.in.rempty();
|
||||
}
|
||||
|
||||
void GSState::UpdateVertexKick()
|
||||
@@ -4269,6 +4264,10 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
|
||||
const u16* RESTRICT index = m_index->buff;
|
||||
const u32 count = m_index->tail;
|
||||
|
||||
// Since adjacent triangles overlap at the edges with AA1, we cannot combine
|
||||
// such triangles, so disable some barrier optimizations.
|
||||
const bool using_aa1 = IsCoverageAlphaSupported();
|
||||
|
||||
const auto GetPoint = [&](int i) -> GSVector4i {
|
||||
if constexpr (primclass == GS_SPRITE_CLASS || primclass == GS_POINT_CLASS)
|
||||
return GSVector4i(v[i].m[1]).upl16(); // Optimize out using the indices.
|
||||
@@ -4280,7 +4279,7 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
|
||||
// Allows faster comparison than using O(n^2) for full pairwise intersections.
|
||||
// Check Virtua Fighter for example.
|
||||
|
||||
if (primclass == GS_TRIANGLE_CLASS && m_quad_check_valid && m_are_quads)
|
||||
if (primclass == GS_TRIANGLE_CLASS && m_quad_check_valid && m_are_quads && !using_aa1)
|
||||
{
|
||||
// The triangles-are-quads check already ensures that there is no overlap.
|
||||
m_drawlist.push_back(m_index->tail / n);
|
||||
@@ -4293,7 +4292,7 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
|
||||
}
|
||||
|
||||
PRIM_OVERLAP overlap = PRIM_OVERLAP_NO;
|
||||
bool check_quads = (primclass == GS_TRIANGLE_CLASS);
|
||||
bool check_quads = (primclass == GS_TRIANGLE_CLASS) && !using_aa1;
|
||||
|
||||
u32 i = 0;
|
||||
u32 skip = 0; // Number of indices to skip if we have the bbox from the previous iteration.
|
||||
@@ -4643,20 +4642,20 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
|
||||
};
|
||||
|
||||
// First check: see if the triangles are part of a triangle strip.
|
||||
if (!got_bbox)
|
||||
if (!got_bbox && !using_aa1)
|
||||
{
|
||||
got_bbox = CheckTriangleStrips(j, skip, bbox, saved_tristrip);
|
||||
}
|
||||
|
||||
// Second check: see if the triangles are part of triangle fan.
|
||||
if (!got_bbox)
|
||||
if (!got_bbox && !using_aa1)
|
||||
{
|
||||
got_bbox = CheckTriangleQuads.template operator()<1>(j, skip, bbox);
|
||||
}
|
||||
|
||||
// Third check: see if a pair of triangles are an axis-aligned quad.
|
||||
// This doesn't require indices to match like the tristrip check.
|
||||
if (!got_bbox && check_quads)
|
||||
if (!got_bbox && check_quads && !using_aa1)
|
||||
{
|
||||
got_bbox = GetBBoxAxisAlignedTriangles(j, skip, bbox);
|
||||
|
||||
@@ -4670,6 +4669,10 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
|
||||
got_bbox = GetBBox(j, skip, bbox);
|
||||
}
|
||||
|
||||
// Expand for AA1 edges overlapping.
|
||||
if (using_aa1)
|
||||
bbox = bbox.ExpandOne();
|
||||
|
||||
// Avoid degenerate bbox.
|
||||
bbox = bbox.FixDegenerate();
|
||||
|
||||
@@ -4729,7 +4732,7 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap(bool save_drawlist)
|
||||
if (m_vertex->next < 4)
|
||||
return PRIM_OVERLAP_NO;
|
||||
|
||||
if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
|
||||
if (m_vt.m_primclass == GS_TRIANGLE_CLASS && !IsCoverageAlphaSupported())
|
||||
return (m_index->tail == 6 && TrianglesAreQuads()) ? PRIM_OVERLAP_NO : PRIM_OVERLAP_UNKNOW;
|
||||
else if (m_vt.m_primclass != GS_SPRITE_CLASS)
|
||||
return PRIM_OVERLAP_UNKNOW; // maybe, maybe not
|
||||
@@ -5833,6 +5836,7 @@ template <u32 prim, bool auto_flush>
|
||||
__forceinline void GSState::VertexKick(u32 skip)
|
||||
{
|
||||
constexpr u32 n = NumIndicesForPrim(prim);
|
||||
constexpr int primclass = GSUtil::GetPrimClass(prim);
|
||||
static_assert(n > 0);
|
||||
|
||||
pxAssert(m_vertex->tail < m_vertex->maxcount + 3);
|
||||
@@ -5876,11 +5880,7 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
|
||||
// We maintain the X/Y coordinates for the last 4 vertices, as well as the head for triangle fans, so we can compute
|
||||
// the min/max, and cull degenerate triangles, which saves draws in some cases. Why 4? Mod 4 is cheaper than Mod 3.
|
||||
// These vertices are a full vector containing <X_Fixed_Point, Y_Fixed_Point, X_Integer, Y_Integer>. We use the
|
||||
// integer coordinates for culling at native resolution, and the fixed point for all others. The XY offset has to be
|
||||
// applied, then we split it into the fixed/integer portions.
|
||||
const GSVector4i xy_ofs = new_v1.xxxx().u16to32().sub32(m_xyof);
|
||||
const GSVector4i xy = xy_ofs.blend32<12>(xy_ofs.sra32<4>());
|
||||
const GSVector4i xy = new_v1.xxxx().u16to32().sub32(m_xyof);
|
||||
m_vertex->xy[xy_tail & 3] = xy;
|
||||
|
||||
// Backup head for triangle fans so we can read it later, otherwise it'll get lost after the 4th vertex.
|
||||
@@ -5910,73 +5910,66 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
// Skip draws when scissor is out of range (i.e. bottom-right is less than top-left), since everything will get clipped.
|
||||
skip |= static_cast<u32>(m_scissor_invalid);
|
||||
|
||||
GSVector4i pmin, pmax;
|
||||
GSVector4i bbox;
|
||||
if (skip == 0)
|
||||
{
|
||||
const GSVector4i v0 = m_vertex->xy[(xy_tail - 1) & 3];
|
||||
const GSVector4i v1 = m_vertex->xy[(xy_tail - 2) & 3];
|
||||
const GSVector4i v2 = (prim == GS_TRIANGLEFAN) ? m_vertex->xyhead : m_vertex->xy[(xy_tail - 3) & 3];
|
||||
|
||||
switch (prim)
|
||||
if constexpr (n == 1)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
pmin = v0;
|
||||
pmax = v0;
|
||||
break;
|
||||
case GS_LINELIST:
|
||||
case GS_LINESTRIP:
|
||||
case GS_SPRITE:
|
||||
pmin = v0.min_i32(v1);
|
||||
pmax = v0.max_i32(v1);
|
||||
break;
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
case GS_TRIANGLEFAN:
|
||||
pmin = v0.min_i32(v1.min_i32(v2));
|
||||
pmax = v0.max_i32(v1.max_i32(v2));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
bbox = v0;
|
||||
}
|
||||
else if constexpr (n == 2)
|
||||
{
|
||||
bbox = v0.runion(v1);
|
||||
}
|
||||
else if constexpr (n == 3)
|
||||
{
|
||||
bbox = v0.runion(v1).runion(v2);
|
||||
}
|
||||
|
||||
GSVector4i test = pmax.lt32(m_scissor_cull_min) | pmin.gt32(m_scissor_cull_max);
|
||||
|
||||
switch (prim)
|
||||
if constexpr (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS)
|
||||
{
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
case GS_TRIANGLEFAN:
|
||||
case GS_SPRITE:
|
||||
if (m_nativeres)
|
||||
{
|
||||
// Discard degenerate triangles which don't cover at least one pixel. Since the vertices are in native
|
||||
// resolution space, we can use the integer locations. When upscaling, we can't, because a primitive which
|
||||
// does not span a single pixel at 1x may span multiple pixels at higher resolutions.
|
||||
const GSVector4i degen_test = pmin.eq32(pmax);
|
||||
test |= m_nativeres ? degen_test.zwzw() : degen_test;
|
||||
// For triangles and sprites at native res take the interior pixel centers.
|
||||
const GSVector4i interior = (bbox + GSVector4i(0xF, 0xF, -1, -1)) & GSVector4i(~0xF);
|
||||
bbox = interior + GSVector4i(0, 0, 1, 1); // +1 to bottom/right so empty test works correctly.
|
||||
}
|
||||
else
|
||||
{
|
||||
// For upscaling, remove bottom/right subtexels.
|
||||
bbox -= ((bbox & GSVector4i(0xF)) == GSVector4i(0)) & GSVector4i(0, 0, 1, 1);
|
||||
}
|
||||
|
||||
// For AA1 triangles and lines, expand the bounds by 1 pixel on all sides.
|
||||
// Note: redundant check for the AA1 flag to avoid calling a function if not needed.
|
||||
if (PRIM->AA1 && IsCoverageAlphaSupported())
|
||||
{
|
||||
bbox += GSVector4i(-0x10, -0x10, 0x10, 0x10);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (prim)
|
||||
// Do scissor test.
|
||||
const GSVector4i bbox_ex = bbox + GSVector4i(0, 0, 1, 1); // Exclusive coords for the scissor test.
|
||||
const GSVector4i& scissor = m_context->scissor.cull;
|
||||
u32 test = static_cast<u32>(!bbox_ex.rintersects(scissor));
|
||||
|
||||
// Test for empty bbox.
|
||||
if constexpr (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS)
|
||||
{
|
||||
case GS_TRIANGLELIST:
|
||||
case GS_TRIANGLESTRIP:
|
||||
case GS_TRIANGLEFAN:
|
||||
test = (test | v0.eq64(v1)) | (v1.eq64(v2) | v0.eq64(v2));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
test |= static_cast<u32>(bbox.rempty());
|
||||
}
|
||||
|
||||
#ifndef ARCH_ARM64
|
||||
// We only care about the xy passing the skip test. zw is the offset coordinates for native culling.
|
||||
skip |= test.mask() & 0xff;
|
||||
#else
|
||||
// mask() is slow on ARM, so just pull the bits out instead, thankfully we only care about the first 4 bytes.
|
||||
skip |= (static_cast<u64>(test.extract64<0>()) & UINT64_C(0x8080808080808080)) != 0;
|
||||
#endif
|
||||
// Test for degenerate triangle.
|
||||
if constexpr (primclass == GS_TRIANGLE_CLASS)
|
||||
{
|
||||
test |= static_cast<u32>(v0.eq(v1)) | static_cast<u32>(v1.eq(v2)) | static_cast<u32>(v0.eq(v2));
|
||||
}
|
||||
|
||||
skip |= test;
|
||||
}
|
||||
|
||||
if (skip != 0)
|
||||
@@ -6086,13 +6079,12 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
ASSUME(0);
|
||||
}
|
||||
|
||||
// Update rectangle for the current draw. We can use the re-integer coordinates from min/max here.
|
||||
const GSVector4i draw_min = pmin.zwzw();
|
||||
const GSVector4i draw_max = pmax;
|
||||
if (m_index->tail != n)
|
||||
temp_draw_rect = temp_draw_rect.min_i32(draw_min).blend32<12>(temp_draw_rect.max_i32(draw_max));
|
||||
// Update rectangle for the current draw. Needs exclusive endpoints.
|
||||
const GSVector4i draw_rect = bbox.sra32<4>() + GSVector4i(0, 0, 1, 1);
|
||||
if (m_vertex->tail != n)
|
||||
temp_draw_rect = temp_draw_rect.runion(draw_rect);
|
||||
else
|
||||
temp_draw_rect = draw_min.blend32<12>(draw_max);
|
||||
temp_draw_rect = draw_rect;
|
||||
temp_draw_rect = temp_draw_rect.rintersect(m_context->scissor.in);
|
||||
|
||||
constexpr u32 max_vertices = MaxVerticesForPrim(prim);
|
||||
@@ -6424,12 +6416,10 @@ void GSState::CalcAlphaMinMax(const int tex_alpha_min, const int tex_alpha_max)
|
||||
// Limit max to 255 as we send 500 when we don't know, makes calculating 24/16bit easier.
|
||||
int min = tex_alpha_min, max = std::min(tex_alpha_max, 255);
|
||||
|
||||
if (IsCoverageAlpha())
|
||||
if (IsCoverageAlphaFixedOne())
|
||||
{
|
||||
// HW renderer doesn't currently support AA, so its min is 128.
|
||||
// If we add AA support to the HW renderer, this will need to be changed.
|
||||
// (Will probably only be supported with ROV/FBFetch so we would want to check for that.)
|
||||
min = GSIsHardwareRenderer() ? 128 : 0;
|
||||
// HW renderer doesn't support AA1, assume alpha is constant 128.
|
||||
min = 128;
|
||||
max = 128;
|
||||
}
|
||||
else
|
||||
@@ -6509,7 +6499,34 @@ void GSState::CalcAlphaMinMax(const int tex_alpha_min, const int tex_alpha_max)
|
||||
|
||||
m_vt.m_alpha.min = min;
|
||||
m_vt.m_alpha.max = max;
|
||||
m_vt.m_alpha.depth_min = min;
|
||||
m_vt.m_alpha.depth_max = max;
|
||||
m_vt.m_alpha.valid = true;
|
||||
|
||||
if (IsCoverageAlphaSupported())
|
||||
{
|
||||
// Expand the alpha range depending on what the AA1 can do to the alpha.
|
||||
|
||||
if (PRIM->ABE)
|
||||
{
|
||||
// ABE==1: Coverage is used for alpha only used when incoming alpha is exactly 128.
|
||||
// If 128 is in the incoming range, expand it down to 0, since edges could be 0-127.
|
||||
// Don't change depth min, since depth isn't written on the edges.
|
||||
if (min <= 128 && 128 <= max)
|
||||
{
|
||||
m_vt.m_alpha.min = std::min(0, min);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ABE==0: Coverage is always used for alpha, so assume exactly 0-128.
|
||||
// Assume exactly 128 for depth, since depth isn't written on the edges.
|
||||
m_vt.m_alpha.min = 0;
|
||||
m_vt.m_alpha.max = 128;
|
||||
m_vt.m_alpha.depth_min = 128;
|
||||
m_vt.m_alpha.depth_max = 128;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSState::CorrectATEAlphaMinMax(const u32 atst, const int aref)
|
||||
@@ -6575,107 +6592,127 @@ bool GSState::TryAlphaTest(u32& fm, u32& zm)
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
ASSUME(0);
|
||||
pxFailRel("Impossible.");
|
||||
break;
|
||||
}
|
||||
|
||||
bool pass = true;
|
||||
enum AlphaTestResult
|
||||
{
|
||||
UNKNOWN,
|
||||
ALL_PASS,
|
||||
ALL_FAIL,
|
||||
};
|
||||
|
||||
AlphaTestResult result, depth_result;
|
||||
|
||||
if (m_context->TEST.ATST == ATST_NEVER)
|
||||
{
|
||||
pass = false; // Shortcut to avoid GetAlphaMinMax below
|
||||
// Shortcut for NEVER to avoid GetAlphaMinMax below.
|
||||
result = ALL_FAIL;
|
||||
depth_result = ALL_FAIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
const GSVertexTrace::VertexAlpha& aminmax = GetAlphaMinMax();
|
||||
const int amin = aminmax.min;
|
||||
const int amax = aminmax.max;
|
||||
|
||||
const int aref = m_context->TEST.AREF;
|
||||
|
||||
switch (m_context->TEST.ATST)
|
||||
{
|
||||
case ATST_NEVER:
|
||||
pass = false;
|
||||
break;
|
||||
case ATST_ALWAYS:
|
||||
pass = true;
|
||||
break;
|
||||
case ATST_LESS:
|
||||
if (amax < aref)
|
||||
pass = true;
|
||||
else if (amin >= aref)
|
||||
pass = false;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case ATST_LEQUAL:
|
||||
if (amax <= aref)
|
||||
pass = true;
|
||||
else if (amin > aref)
|
||||
pass = false;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case ATST_EQUAL:
|
||||
if (amin == aref && amax == aref)
|
||||
pass = true;
|
||||
else if (amin > aref || amax < aref)
|
||||
pass = false;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case ATST_GEQUAL:
|
||||
if (amin >= aref)
|
||||
pass = true;
|
||||
else if (amax < aref)
|
||||
pass = false;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case ATST_GREATER:
|
||||
if (amin > aref)
|
||||
pass = true;
|
||||
else if (amax <= aref)
|
||||
pass = false;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
case ATST_NOTEQUAL:
|
||||
if (amin == aref && amax == aref)
|
||||
pass = false;
|
||||
else if (amin > aref || amax < aref)
|
||||
pass = true;
|
||||
else
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
ASSUME(0);
|
||||
}
|
||||
const auto GetResult = [&](int amin, int amax) {
|
||||
switch (m_context->TEST.ATST)
|
||||
{
|
||||
case ATST_NEVER:
|
||||
return ALL_FAIL;
|
||||
case ATST_ALWAYS:
|
||||
return ALL_PASS;
|
||||
case ATST_LESS:
|
||||
{
|
||||
if (amax < aref)
|
||||
return ALL_PASS;
|
||||
else if (amin >= aref)
|
||||
return ALL_FAIL;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
case ATST_LEQUAL:
|
||||
{
|
||||
if (amax <= aref)
|
||||
return ALL_PASS;
|
||||
else if (amin > aref)
|
||||
return ALL_FAIL;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
case ATST_EQUAL:
|
||||
{
|
||||
if (amin == aref && amax == aref)
|
||||
return ALL_PASS;
|
||||
else if (amin > aref || amax < aref)
|
||||
return ALL_FAIL;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
case ATST_GEQUAL:
|
||||
{
|
||||
if (amin >= aref)
|
||||
return ALL_PASS;
|
||||
else if (amax < aref)
|
||||
return ALL_FAIL;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
case ATST_GREATER:
|
||||
{
|
||||
if (amin > aref)
|
||||
return ALL_PASS;
|
||||
else if (amax <= aref)
|
||||
return ALL_FAIL;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
case ATST_NOTEQUAL:
|
||||
{
|
||||
if (amin == aref && amax == aref)
|
||||
return ALL_FAIL;
|
||||
else if (amin > aref || amax < aref)
|
||||
return ALL_PASS;
|
||||
else
|
||||
return UNKNOWN;
|
||||
}
|
||||
default:
|
||||
pxFailRel("Impossible");
|
||||
return UNKNOWN;
|
||||
}
|
||||
};
|
||||
|
||||
result = GetResult(aminmax.min, aminmax.max);
|
||||
depth_result = (result != UNKNOWN) ? result : GetResult(aminmax.depth_min, aminmax.depth_max);
|
||||
}
|
||||
|
||||
if (!pass)
|
||||
const u32 fail_fm = (result == ALL_FAIL) ? 0xffffffff : 0x0;
|
||||
const u32 fail_zm = (depth_result == ALL_FAIL) ? 0xffffffff : 0x0;
|
||||
|
||||
switch (fail_type)
|
||||
{
|
||||
switch (fail_type)
|
||||
{
|
||||
case AFAIL_KEEP:
|
||||
fm = zm = 0xffffffff;
|
||||
break;
|
||||
case AFAIL_FB_ONLY:
|
||||
zm = 0xffffffff;
|
||||
break;
|
||||
case AFAIL_ZB_ONLY:
|
||||
fm = 0xffffffff;
|
||||
break;
|
||||
case AFAIL_RGB_ONLY:
|
||||
fm |= 0xff000000;
|
||||
zm = 0xffffffff;
|
||||
break;
|
||||
default:
|
||||
ASSUME(0);
|
||||
}
|
||||
case AFAIL_KEEP:
|
||||
fm |= fail_fm;
|
||||
zm |= fail_zm;
|
||||
break;
|
||||
case AFAIL_FB_ONLY:
|
||||
zm |= fail_zm;
|
||||
break;
|
||||
case AFAIL_ZB_ONLY:
|
||||
fm |= fail_fm;
|
||||
break;
|
||||
case AFAIL_RGB_ONLY:
|
||||
fm |= (fail_fm & 0xff000000);
|
||||
zm |= fail_zm;
|
||||
break;
|
||||
default:
|
||||
pxFailRel("Impossible.");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
return result != UNKNOWN;
|
||||
}
|
||||
|
||||
bool GSState::IsFlatShaded()
|
||||
@@ -6735,7 +6772,18 @@ bool GSState::IsMipMapActive()
|
||||
|
||||
bool GSState::IsCoverageAlpha()
|
||||
{
|
||||
return !PRIM->ABE && PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS);
|
||||
return PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS);
|
||||
}
|
||||
|
||||
bool GSState::IsCoverageAlphaFixedOne()
|
||||
{
|
||||
return IsCoverageAlpha() && !PRIM->ABE && !IsCoverageAlphaSupported();
|
||||
}
|
||||
|
||||
bool GSState::IsCoverageAlphaSupported()
|
||||
{
|
||||
pxFailRel("Not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
GIFRegTEX0 GSState::GetTex0Layer(u32 lod)
|
||||
|
||||
+5
-5
@@ -131,8 +131,6 @@ protected:
|
||||
|
||||
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;
|
||||
@@ -224,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);
|
||||
|
||||
@@ -258,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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -1395,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);
|
||||
@@ -1451,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)
|
||||
@@ -1496,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);
|
||||
}
|
||||
|
||||
@@ -1526,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);
|
||||
@@ -1579,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 +2493,66 @@ 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.
|
||||
@@ -2579,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);
|
||||
@@ -2610,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);
|
||||
|
||||
@@ -2731,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);
|
||||
@@ -2745,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)
|
||||
@@ -2755,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
|
||||
{
|
||||
@@ -2767,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))
|
||||
@@ -2802,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 && config.tex != config.rt)
|
||||
{
|
||||
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 && config.tex != config.rt)
|
||||
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);
|
||||
|
||||
@@ -2836,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.
|
||||
@@ -2849,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())
|
||||
{
|
||||
@@ -2861,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)
|
||||
@@ -2895,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)
|
||||
@@ -2911,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)
|
||||
@@ -2937,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;
|
||||
@@ -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
|
||||
@@ -4296,9 +4369,10 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -790,11 +790,14 @@ void GSRendererHW::DetectTextureShuffle()
|
||||
|
||||
void GSRendererHW::DetectTextureShuffleSecondPass(GSTextureCache::Target* rt, GSTextureCache::Source* tex)
|
||||
{
|
||||
const auto HasLowerOnes = [&](u32 x) { return x != 0 && (x & (x + 1)) == 0; };
|
||||
if (!m_process_texture || !rt)
|
||||
{
|
||||
GL_INS("HW: Texture shuffle detection (2): Not detected.");
|
||||
m_texture_shuffle.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
const GSLocalMemory::psm_t& tex_psm = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM];
|
||||
const GSLocalMemory::psm_t& frame_psm = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM];
|
||||
|
||||
if (frame_psm.bpp != 16)
|
||||
{
|
||||
GL_INS("HW: Texture shuffle detection (2): Failed (not 16 bit RT).");
|
||||
@@ -804,7 +807,7 @@ void GSRendererHW::DetectTextureShuffleSecondPass(GSTextureCache::Target* rt, GS
|
||||
|
||||
if (m_texture_shuffle)
|
||||
{
|
||||
if (tex->m_32_bits_fmt)
|
||||
if (tex && tex->m_32_bits_fmt)
|
||||
{
|
||||
GL_INS("HW: Texture shuffle detection (2): Passed.");
|
||||
}
|
||||
@@ -812,9 +815,9 @@ void GSRendererHW::DetectTextureShuffleSecondPass(GSTextureCache::Target* rt, GS
|
||||
{
|
||||
// Detects when the source texture is really a 16 bit texture instead of 32 bit being reinterpreted as 16 bit.
|
||||
// Make sure it's opaque and not bilinear to reduce false positives.
|
||||
|
||||
const auto HasLowerOnes = [&](u32 x) { return x != 0 && (x & (x + 1)) == 0; };
|
||||
if (m_cached_ctx.TEX0.TBP0 != m_cached_ctx.FRAME.Block() &&
|
||||
rt->m_32_bits_fmt == true && IsOpaque() && !m_vt.IsRealLinear() &&
|
||||
rt && rt->m_32_bits_fmt == true && IsOpaque() && !m_vt.IsRealLinear() &&
|
||||
HasLowerOnes(m_cached_ctx.FRAME.FBMSK))
|
||||
{
|
||||
GL_INS("HW: Texture shuffle detection (2): Passed (real 16 bit source).");
|
||||
@@ -832,10 +835,10 @@ void GSRendererHW::DetectTextureShuffleSecondPass(GSTextureCache::Target* rt, GS
|
||||
// Last ditch check for reinterpreting a 32 bit source and RT as 16 bits.
|
||||
// These "shuffles" appear to not do anything useful for games, but using the texture shuffle
|
||||
// path helps to maintain correct sizes in the texture cache. Occurs in NFS Most Wanted.
|
||||
|
||||
const GSLocalMemory::psm_t& tex_psm = GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM];
|
||||
if (PRIM->TME &&
|
||||
(m_vt.m_primclass == GS_SPRITE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS && TrianglesAreQuads(true)) &&
|
||||
(tex_psm.bpp == 16) && (frame_psm.bpp == 16) && rt->m_32_bits_fmt && tex->m_32_bits_fmt)
|
||||
((m_vt.m_primclass == GS_SPRITE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS) && TrianglesAreQuads(true)) &&
|
||||
(tex_psm.bpp == 16) && (frame_psm.bpp == 16) && rt && rt->m_32_bits_fmt && tex && tex->m_32_bits_fmt)
|
||||
{
|
||||
GL_INS("HW: Texture shuffle detection (2): Passed (HACK: reinterpreting both source/RT as 16 bit).");
|
||||
m_texture_shuffle.type = TextureShuffleType::HackShuffle;
|
||||
@@ -2918,11 +2921,11 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
|
||||
// Test if we can optimize Alpha Test as a NOP
|
||||
m_cached_ctx.TEST.ATE = m_cached_ctx.TEST.ATE && !GSRenderer::TryAlphaTest(fm, zm);
|
||||
m_cached_ctx.TEST.ATE = !!m_cached_ctx.TEST.ATE && !GSRenderer::TryAlphaTest(fm, zm);
|
||||
|
||||
// Need to fix the alpha test, since the alpha will be fixed to 1.0 if ABE is disabled and AA1 is enabled
|
||||
// So if it doesn't meet the condition, always fail, if it does, always pass (turn off the test).
|
||||
if (IsCoverageAlpha() && m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > 1)
|
||||
if (IsCoverageAlphaFixedOne() && m_cached_ctx.TEST.ATE && m_cached_ctx.TEST.ATST > 1)
|
||||
{
|
||||
const float aref = static_cast<float>(m_cached_ctx.TEST.AREF);
|
||||
const int old_ATST = m_cached_ctx.TEST.ATST;
|
||||
@@ -2992,7 +2995,7 @@ void GSRendererHW::Draw()
|
||||
// I hate that I have to do this, but some games (like Pac-Man World Rally) troll us by causing a flush with degenerate triangles, so we don't have all available information about the next draw.
|
||||
// So we have to check when the next draw happens if our frame has changed or if it's become recursive.
|
||||
const bool has_colclip_texture = g_gs_device->GetColorClipTexture() != nullptr;
|
||||
if (!no_rt && has_colclip_texture && (m_conf.colclip_frame.FBP != m_cached_ctx.FRAME.FBP || m_conf.colclip_frame.Block() == m_cached_ctx.TEX0.TBP0))
|
||||
if (!no_rt && has_colclip_texture && (m_conf.colclip_frame.FBP != m_cached_ctx.FRAME.FBP || (PRIM->TME && m_conf.colclip_frame.Block() == m_cached_ctx.TEX0.TBP0)))
|
||||
{
|
||||
GIFRegTEX0 FRAME;
|
||||
FRAME.TBP0 = m_conf.colclip_frame.Block();
|
||||
@@ -4388,15 +4391,15 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass texture shuffle detection using RT and source.
|
||||
DetectTextureShuffleSecondPass(rt, src);
|
||||
|
||||
if (m_process_texture)
|
||||
{
|
||||
GIFRegCLAMP MIP_CLAMP = m_cached_ctx.CLAMP;
|
||||
|
||||
if (rt)
|
||||
{
|
||||
// Second pass texture shuffle detection using RT and source.
|
||||
DetectTextureShuffleSecondPass(rt, src);
|
||||
|
||||
if (m_texture_shuffle)
|
||||
{
|
||||
if (IsSplitTextureShuffle(rt->m_TEX0, rt->m_valid))
|
||||
@@ -5362,7 +5365,7 @@ void GSRendererHW::HandleProvokingVertexFirst()
|
||||
}
|
||||
}
|
||||
|
||||
void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert_backup)
|
||||
void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert_backup, const bool no_rt)
|
||||
{
|
||||
GL_PUSH("HW: IA");
|
||||
|
||||
@@ -5376,6 +5379,7 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
|
||||
|
||||
const bool unscale_pt_ln = !GSConfig.UserHacks_DisableSafeFeatures && (target_scale != 1.0f);
|
||||
const GSDevice::FeatureSupport features = g_gs_device->Features();
|
||||
const bool draw_aa1 = !no_rt && PRIM->AA1 && features.aa1;
|
||||
|
||||
pxAssert(VerifyIndices());
|
||||
|
||||
@@ -5419,7 +5423,18 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
|
||||
{
|
||||
m_conf.topology = GSHWDrawConfig::Topology::Line;
|
||||
m_conf.indices_per_prim = 2;
|
||||
if (unscale_pt_ln)
|
||||
if (draw_aa1)
|
||||
{
|
||||
// AA1 expansion uses a similar path as upscale expansion but it is used
|
||||
// for both upscaling and native resolution drawing.
|
||||
GL_INS("HW: AA1 line expand.");
|
||||
m_conf.vs.expand = GSHWDrawConfig::VSExpand::LineAA1;
|
||||
m_conf.cb_vs.point_size = GSVector2(16.0f * sx, 16.0f * sy);
|
||||
m_conf.topology = GSHWDrawConfig::Topology::Triangle;
|
||||
m_conf.indices_per_prim = 6;
|
||||
ExpandLineIndices();
|
||||
}
|
||||
else if (unscale_pt_ln)
|
||||
{
|
||||
if (features.line_expand)
|
||||
{
|
||||
@@ -5476,8 +5491,19 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
|
||||
|
||||
case GS_TRIANGLE_CLASS:
|
||||
{
|
||||
m_conf.topology = GSHWDrawConfig::Topology::Triangle;
|
||||
m_conf.indices_per_prim = 3;
|
||||
if (draw_aa1)
|
||||
{
|
||||
GL_INS("HW: AA1 triangle expand.");
|
||||
m_conf.vs.expand = GSHWDrawConfig::VSExpand::TriangleAA1;
|
||||
m_conf.cb_vs.point_size = GSVector2(16.0f * sx, 16.0f * sy);
|
||||
m_conf.topology = GSHWDrawConfig::Topology::Triangle;
|
||||
m_conf.indices_per_prim = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_conf.topology = GSHWDrawConfig::Topology::Triangle;
|
||||
m_conf.indices_per_prim = 3;
|
||||
}
|
||||
|
||||
// See note above in GS_SPRITE_CLASS.
|
||||
if (m_vt.m_accurate_stq && m_vt.m_eq.stq) [[unlikely]]
|
||||
@@ -5765,6 +5791,51 @@ void GSRendererHW::DetermineAlphaScaling(GSTextureCache::Target* rt, GSTextureCa
|
||||
}
|
||||
}
|
||||
|
||||
void GSRendererHW::EmulateAA1()
|
||||
{
|
||||
const GSDevice::FeatureSupport& features = g_gs_device->Features();
|
||||
|
||||
pxAssert(!features.aa1 || features.feedback_loops());
|
||||
|
||||
if (IsCoverageAlphaSupported())
|
||||
{
|
||||
m_conf.ps.abe = PRIM->ABE; // ABE flag determines how coverage is used for alpha.
|
||||
|
||||
if (m_vt.m_primclass == GS_LINE_CLASS)
|
||||
{
|
||||
GL_INS("HW: AA1 lines. No depth write.");
|
||||
|
||||
// AA1: Z is not written on lines since coverage is always less than 0x80.
|
||||
m_conf.depth.zwe = false;
|
||||
m_cached_ctx.ZBUF.ZMSK = 1;
|
||||
|
||||
m_conf.ps.aa1 = GSHWDrawConfig::PS_AA1::LINE;
|
||||
}
|
||||
else if (m_vt.m_primclass == GS_TRIANGLE_CLASS)
|
||||
{
|
||||
// Force SW depth so that Z writes can be prevented for edge pixels.
|
||||
if (m_cached_ctx.DepthWrite())
|
||||
{
|
||||
GL_INS("HW: AA1 triangles with depth feedback.");
|
||||
|
||||
m_conf.ps.aa1 = GSHWDrawConfig::PS_AA1::TRIANGLE_SW_Z; // Allows discarding depth on edge pixels.
|
||||
|
||||
ConfigureDepthFeedback(); // Enable barriers/SW depth test.
|
||||
}
|
||||
else
|
||||
{
|
||||
GL_INS("HW: AA1 triangles with no depth write.");
|
||||
|
||||
m_conf.ps.aa1 = GSHWDrawConfig::PS_AA1::TRIANGLE; // No special depth handling.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pxFail("Unsupported primclass for AA1"); // Impossible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GSRendererHW::EmulateDATEEarlyFail(DATEOptions& date, GSTextureCache::Target* rt)
|
||||
{
|
||||
if (!date.enabled)
|
||||
@@ -5774,7 +5845,7 @@ bool GSRendererHW::EmulateDATEEarlyFail(DATEOptions& date, GSTextureCache::Targe
|
||||
if (m_cached_ctx.TEST.DATM == 0)
|
||||
{
|
||||
// Some pixels are >= 1 so some fail, or some pixels get written but the written alpha matches or exceeds 1 (so overlap doesn't always pass).
|
||||
date.enabled = rt->m_alpha_max >= 128 || (is_overlap_alpha && rt->m_alpha_min < 128 && (GetAlphaMinMax().max >= 128 || (m_context->FBA.FBA || IsCoverageAlpha())));
|
||||
date.enabled = rt->m_alpha_max >= 128 || (is_overlap_alpha && rt->m_alpha_min < 128 && (GetAlphaMinMax().max >= 128 || (m_context->FBA.FBA || IsCoverageAlphaFixedOne())));
|
||||
|
||||
// All pixels fail.
|
||||
if (date.enabled && rt->m_alpha_min >= 128)
|
||||
@@ -5783,7 +5854,7 @@ bool GSRendererHW::EmulateDATEEarlyFail(DATEOptions& date, GSTextureCache::Targe
|
||||
else
|
||||
{
|
||||
// Some pixels are < 1 so some fail, or some pixels get written but the written alpha goes below 1 (so overlap doesn't always pass).
|
||||
date.enabled = rt->m_alpha_min < 128 || (is_overlap_alpha && rt->m_alpha_max >= 128 && (GetAlphaMinMax().min < 128 && !(m_context->FBA.FBA || IsCoverageAlpha())));
|
||||
date.enabled = rt->m_alpha_min < 128 || (is_overlap_alpha && rt->m_alpha_max >= 128 && (GetAlphaMinMax().min < 128 && !(m_context->FBA.FBA || IsCoverageAlphaFixedOne())));
|
||||
|
||||
// All pixels fail.
|
||||
if (date.enabled && rt->m_alpha_max < 128)
|
||||
@@ -5820,21 +5891,35 @@ void GSRendererHW::EmulateDATESelectMethod(DATEOptions& date_options, GSTextureC
|
||||
if (features.framebuffer_fetch)
|
||||
{
|
||||
// Full DATE is "free" with framebuffer fetch. The barrier gets cleared below.
|
||||
GL_PERF("DATE: Accurate with framebuffer fetch");
|
||||
date_options.barrier = true;
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
else if ((features.texture_barrier && m_prim_overlap == PRIM_OVERLAP_NO) || m_texture_shuffle)
|
||||
else if (features.feedback_loops() && IsCoverageAlphaSupported())
|
||||
{
|
||||
GL_PERF("DATE: Accurate with %s", (features.texture_barrier && m_prim_overlap == PRIM_OVERLAP_NO) ? "no overlap" : "texture shuffle");
|
||||
if (features.texture_barrier)
|
||||
{
|
||||
m_conf.require_full_barrier = true;
|
||||
date_options.barrier = true;
|
||||
}
|
||||
// We're using AA1 for this draw so use only full barrier DATE, to avoid the complications
|
||||
// with stencil/primid setup with AA1 vertex shaders. AA1 triangles usually require full barriers anyway.
|
||||
GL_PERF("DATE: Accurate with IsCoverageAlphaSupported");
|
||||
date_options.barrier = true;
|
||||
m_conf.require_full_barrier = true;
|
||||
}
|
||||
else if ((features.texture_barrier && m_prim_overlap == PRIM_OVERLAP_NO))
|
||||
{
|
||||
// We only enable this for texture barriers as it's fast, multidraw fb copy might be slower.
|
||||
GL_PERF("DATE: Accurate with no overlap");
|
||||
m_conf.require_full_barrier = true;
|
||||
date_options.barrier = true;
|
||||
}
|
||||
else if (features.feedback_loops() && m_texture_shuffle)
|
||||
{
|
||||
// Ensure texture shuffles run with full barrier DATE.
|
||||
GL_PERF("DATE: Accurate with texture shuffle");
|
||||
m_conf.require_full_barrier = true;
|
||||
date_options.barrier = true;
|
||||
}
|
||||
// When Blending is disabled and Edge Anti Aliasing is enabled,
|
||||
// the output alpha is Coverage (which we force to 128) so DATE will fail/pass guaranteed on second pass.
|
||||
else if (m_conf.colormask.wa && (m_context->FBA.FBA || IsCoverageAlpha()) && features.stencil_buffer)
|
||||
else if (m_conf.colormask.wa && (m_context->FBA.FBA || IsCoverageAlphaFixedOne()) && features.stencil_buffer)
|
||||
{
|
||||
GL_PERF("DATE: Fast with FBA, all pixels will be >= 128");
|
||||
date_options.stencil_one = !m_cached_ctx.TEST.DATM;
|
||||
@@ -5864,7 +5949,7 @@ void GSRendererHW::EmulateDATESelectMethod(DATEOptions& date_options, GSTextureC
|
||||
m_conf.require_full_barrier = true;
|
||||
date_options.barrier = true;
|
||||
}
|
||||
else if ((features.texture_barrier || features.multidraw_fb_copy) && m_conf.require_full_barrier)
|
||||
else if (features.feedback_loops() && m_conf.require_full_barrier)
|
||||
{
|
||||
// Full barrier is enabled (likely sw fbmask), we need to use date barrier.
|
||||
GL_PERF("DATE: Accurate with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max);
|
||||
@@ -5876,7 +5961,7 @@ void GSRendererHW::EmulateDATESelectMethod(DATEOptions& date_options, GSTextureC
|
||||
GL_PERF("DATE: Accurate with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max);
|
||||
date_options.primid = true;
|
||||
}
|
||||
else if (features.texture_barrier || features.multidraw_fb_copy)
|
||||
else if (features.feedback_loops())
|
||||
{
|
||||
GL_PERF("DATE: Accurate with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max);
|
||||
m_conf.require_full_barrier = true;
|
||||
@@ -5914,7 +5999,7 @@ void GSRendererHW::EmulateDATEGetConfig(DATEOptions& date_options, bool scale_rt
|
||||
|
||||
// Always swap DATE with DATE_BARRIER if we have barriers on when alpha write is masked.
|
||||
// This is always enabled on VK/GL/DX12 but not on DX11 as copies are slow so we can selectively enable it like now.
|
||||
if (!m_conf.colormask.wa && (m_conf.require_one_barrier || m_conf.require_full_barrier))
|
||||
if (!m_conf.colormask.wa && (m_conf.require_one_barrier || (m_conf.require_full_barrier && features.feedback_loops())))
|
||||
date_options.barrier = true;
|
||||
|
||||
if (m_conf.ps.scanmsk & 2)
|
||||
@@ -5934,17 +6019,25 @@ void GSRendererHW::EmulateDATEGetConfig(DATEOptions& date_options, bool scale_rt
|
||||
else
|
||||
m_conf.datm = static_cast<SetDATM>(m_cached_ctx.TEST.DATM);
|
||||
|
||||
if (m_conf.destination_alpha >= GSHWDrawConfig::DestinationAlphaMode::Stencil &&
|
||||
m_conf.destination_alpha <= GSHWDrawConfig::DestinationAlphaMode::StencilOne && !m_conf.ds)
|
||||
// DATE Stencil always needs a depth stencil texture.
|
||||
const bool date_stencil_needs_ds = !m_conf.ds &&
|
||||
(m_conf.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::Stencil || m_conf.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne);
|
||||
if (date_stencil_needs_ds)
|
||||
{
|
||||
const bool is_one_barrier = (features.texture_barrier && m_conf.require_full_barrier &&
|
||||
(m_prim_overlap == PRIM_OVERLAP_NO || m_conf.ps.shuffle || m_channel_shuffle));
|
||||
const bool need_barrier = m_conf.require_one_barrier || (m_conf.require_full_barrier && features.feedback_loops());
|
||||
if ((temp_ds.reset(g_gs_device->CreateDepthStencil(m_conf.rt->GetWidth(), m_conf.rt->GetHeight(),
|
||||
GSTexture::Format::DepthStencil, false)), temp_ds))
|
||||
{
|
||||
m_conf.ds = temp_ds.get();
|
||||
}
|
||||
else if (features.primitive_id && !(m_conf.ps.scanmsk & 2) && (!m_conf.require_full_barrier || is_one_barrier))
|
||||
else if (need_barrier)
|
||||
{
|
||||
date_options.stencil_one = false;
|
||||
date_options.barrier = true;
|
||||
m_conf.destination_alpha = GSHWDrawConfig::DestinationAlphaMode::Full;
|
||||
DevCon.Warning("HW: Depth buffer creation failed for Stencil Date. Fallback to Full.");
|
||||
}
|
||||
else if (features.primitive_id && !(m_conf.ps.scanmsk & 2))
|
||||
{
|
||||
date_options.stencil_one = false;
|
||||
date_options.primid = true;
|
||||
@@ -5966,7 +6059,7 @@ void GSRendererHW::EmulateDATEGetConfig(DATEOptions& date_options, bool scale_rt
|
||||
}
|
||||
else if (date_options.stencil_one)
|
||||
{
|
||||
const bool multidraw_fb_copy = features.multidraw_fb_copy && (m_conf.require_one_barrier || m_conf.require_full_barrier);
|
||||
const bool multidraw_fb_copy = m_conf.require_one_barrier || (m_conf.require_full_barrier && features.multidraw_fb_copy);
|
||||
if (features.texture_barrier || multidraw_fb_copy)
|
||||
{
|
||||
m_conf.require_one_barrier = true;
|
||||
@@ -6075,8 +6168,7 @@ void GSRendererHW::DetermineBarriers(GSTextureCache::Target* rt)
|
||||
|
||||
// If we use depth feedback directly, we must use barriers for the depth texture.
|
||||
// If we use depth-as-color feedback, then FB fetch can be used for depth also.
|
||||
bool need_barriers_for_depth = m_conf.ps.IsFeedbackLoopDepth() &&
|
||||
(features.depth_feedback == GSDevice::DepthFeedbackSupport::Depth);
|
||||
const bool need_barriers_for_depth = m_conf.ps.IsFeedbackLoopDepth() && features.depth_feedback;
|
||||
|
||||
if (!need_barriers_for_depth)
|
||||
{
|
||||
@@ -6089,19 +6181,18 @@ void GSRendererHW::DetermineBarriers(GSTextureCache::Target* rt)
|
||||
pxAssert(!m_conf.require_full_barrier || !m_conf.ps.colclip_hw);
|
||||
|
||||
// Swap full barrier for one barrier when there's no overlap, or a shuffle.
|
||||
if ((features.texture_barrier || features.multidraw_fb_copy) && m_conf.require_full_barrier && (m_prim_overlap == PRIM_OVERLAP_NO || m_conf.ps.shuffle || m_channel_shuffle))
|
||||
if (features.feedback_loops() && m_conf.require_full_barrier && (m_prim_overlap == PRIM_OVERLAP_NO || m_conf.ps.shuffle || m_channel_shuffle))
|
||||
{
|
||||
m_conf.require_full_barrier = false;
|
||||
m_conf.require_one_barrier = true;
|
||||
}
|
||||
else if (!(features.texture_barrier || features.multidraw_fb_copy))
|
||||
else if (!features.feedback_loops())
|
||||
{
|
||||
// These shouldn't be enabled if texture barriers aren't supported, make sure they are off.
|
||||
m_conf.ps.write_rg = 0;
|
||||
m_conf.require_full_barrier = false;
|
||||
}
|
||||
|
||||
if (m_conf.require_full_barrier && (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
|
||||
if (m_conf.require_full_barrier && features.feedback_loops())
|
||||
{
|
||||
ComputeDrawlistGetSize(rt->m_scale);
|
||||
m_conf.drawlist = &m_drawlist;
|
||||
@@ -6173,9 +6264,8 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GS
|
||||
|
||||
// If date is enabled you need to test the green channel instead of the alpha channel.
|
||||
// Only enable this code in DATE mode to reduce the number of shaders.
|
||||
m_conf.ps.write_rg = (process_rg & SHUFFLE_WRITE) && (features.texture_barrier || features.multidraw_fb_copy) &&
|
||||
m_cached_ctx.TEST.DATE;
|
||||
|
||||
m_conf.ps.write_rg = !!(process_rg & SHUFFLE_WRITE) && !!m_cached_ctx.TEST.DATE;
|
||||
|
||||
m_conf.ps.real16src = m_texture_shuffle.real_16_bit_source;
|
||||
|
||||
// Shuffle that copies B to A, which are in the same column.
|
||||
@@ -6289,7 +6379,7 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GS
|
||||
have been invalidated before subsequent Draws are executed.
|
||||
*/
|
||||
// No blending so hit unsafe path.
|
||||
if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !(features.texture_barrier || features.multidraw_fb_copy))
|
||||
if (!PRIM->ABE || !(~ff_fbmask & ~zero_fbmask & 0x7) || !features.feedback_loops())
|
||||
{
|
||||
GL_INS("HW: FBMASK Unsafe SW emulated fb_mask:%x on %d bits format", m_cached_ctx.FRAME.FBMSK,
|
||||
(m_conf.ps.dst_fmt == GSLocalMemory::PSM_FMT_16) ? 16 : 32);
|
||||
@@ -6593,15 +6683,13 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
{
|
||||
const GIFRegALPHA& ALPHA = m_context->ALPHA;
|
||||
{
|
||||
// AA1: Blending needs to be enabled on draw.
|
||||
const bool AA1 = PRIM->AA1 && (m_vt.m_primclass == GS_LINE_CLASS || m_vt.m_primclass == GS_TRIANGLE_CLASS);
|
||||
// PABE: Check condition early as an optimization, no blending when As < 128.
|
||||
// For Cs*As + Cd*(1 - As) if As is 128 then blending can be disabled as well.
|
||||
const bool PABE_skip = m_draw_env->PABE.PABE &&
|
||||
((GetAlphaMinMax().max < 128) || (GetAlphaMinMax().max == 128 && ALPHA.A == 0 && ALPHA.B == 1 && ALPHA.C == 0 && ALPHA.D == 1));
|
||||
|
||||
// No blending or coverage anti-aliasing so early exit
|
||||
if (PABE_skip || !(NeedsBlending() || AA1))
|
||||
if (PABE_skip || !(NeedsBlending() || IsCoverageAlpha()))
|
||||
{
|
||||
m_conf.blend = {};
|
||||
|
||||
@@ -6801,7 +6889,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
// Condition 2: One barrier is already enabled, prims don't overlap or is a channel shuffle so let's use sw blend instead.
|
||||
// Condition 3: A texture shuffle is unlikely to overlap, so we can prefer full sw blend.
|
||||
// Condition 4: If it's tex in fb draw and there's no overlap prefer sw blend, fb is already being read.
|
||||
const bool prefer_sw_blend = ((features.texture_barrier || features.multidraw_fb_copy) && m_conf.require_full_barrier) || (m_conf.require_one_barrier && (no_prim_overlap || m_channel_shuffle)) || m_conf.ps.shuffle || (no_prim_overlap && (m_conf.tex == m_conf.rt));
|
||||
const bool prefer_sw_blend = (features.feedback_loops() && m_conf.require_full_barrier) || (m_conf.require_one_barrier && (no_prim_overlap || m_channel_shuffle)) || m_conf.ps.shuffle || (no_prim_overlap && (m_conf.tex == m_conf.rt));
|
||||
const bool free_blend = blend_non_recursive // Free sw blending, doesn't require barriers or reading fb
|
||||
|| accumulation_blend; // Mix of hw/sw blending
|
||||
|
||||
@@ -6812,7 +6900,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
const bool blend_multipass_group = blend_multi_pass_support && !features.texture_barrier &&
|
||||
(bmix1_multi_pass1 || bmix1_multi_pass2 || bmix3_multi_pass || (blend_flag & (BLEND_HW3 | BLEND_HW4 | BLEND_HW5 | BLEND_HW6 | BLEND_HW7 | BLEND_HW8 | BLEND_HW9)));
|
||||
|
||||
const bool barriers_supported = features.texture_barrier || features.multidraw_fb_copy;
|
||||
const bool barriers_supported = features.feedback_loops();
|
||||
const bool blend_requires_barrier =
|
||||
// We don't want the cases to be enabled if barriers aren't supported so limit it to no overlap.
|
||||
(no_prim_overlap || barriers_supported)
|
||||
@@ -6830,7 +6918,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
{
|
||||
case AccBlendLevel::Maximum:
|
||||
sw_blending |= true;
|
||||
accumulation_blend &= (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 32);
|
||||
accumulation_blend &= !barriers_supported || (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 32);
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::Full:
|
||||
sw_blending |= m_conf.ps.blend_a != m_conf.ps.blend_b && alpha_c0_high_max_one;
|
||||
@@ -6849,7 +6937,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
case AccBlendLevel::Basic:
|
||||
default:
|
||||
// Prefer sw blend if possible.
|
||||
color_dest_blend &= !(m_channel_shuffle || m_conf.ps.dither);
|
||||
color_dest_blend &= !m_conf.ps.dither;
|
||||
color_dest_blend2 &= !(prefer_sw_blend || m_conf.ps.dither);
|
||||
blend_zero_to_one_range &= !(prefer_sw_blend || m_conf.ps.dither);
|
||||
accumulation_blend &= !prefer_sw_blend;
|
||||
@@ -6880,10 +6968,9 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
}
|
||||
}
|
||||
|
||||
if (m_conf.alpha_test == GSHWDrawConfig::AlphaTestMode::FEEDBACK &&
|
||||
features.depth_feedback == GSDevice::DepthFeedbackSupport::DepthAsRT)
|
||||
if (m_conf.ps.IsFeedbackLoopDepth() && !features.depth_feedback)
|
||||
{
|
||||
// If we are doing feedback alpha test with a second RT we must use SW blending to avoid
|
||||
// If we are doing depth feedback with a second RT we must use SW blending to avoid
|
||||
// mixing dual source blending with multiple render targets.
|
||||
sw_blending = true;
|
||||
color_dest_blend = false;
|
||||
@@ -6979,7 +7066,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
|
||||
m_conf.ps.pabe = 1;
|
||||
}
|
||||
else if (features.texture_barrier || features.multidraw_fb_copy)
|
||||
else if (features.feedback_loops())
|
||||
{
|
||||
// PABE sw blend:
|
||||
// Disable hw/sw mix and do pure sw blend with reading the framebuffer.
|
||||
@@ -7152,7 +7239,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, DATEOptio
|
||||
const bool blend_non_recursive_one_barrier = blend_non_recursive && blend_ad_alpha_masked;
|
||||
if (blend_non_recursive_one_barrier)
|
||||
m_conf.require_one_barrier |= true;
|
||||
else if (features.texture_barrier || features.multidraw_fb_copy)
|
||||
else if (features.feedback_loops())
|
||||
m_conf.require_full_barrier |= !blend_non_recursive;
|
||||
else
|
||||
m_conf.require_one_barrier |= !blend_non_recursive;
|
||||
@@ -7762,7 +7849,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
|
||||
if (shader_emulated_sampler)
|
||||
{
|
||||
m_conf.sampler.biln = 0;
|
||||
m_conf.sampler.aniso = 0;
|
||||
m_conf.ps.sw_aniso = 0;
|
||||
|
||||
// Remove linear from trilinear, since we're doing the bilinear in the shader, and we only want this for mip selection.
|
||||
m_conf.sampler.triln = (trilinear >= static_cast<u8>(GS_MIN_FILTER::Linear_Mipmap_Nearest)) ?
|
||||
@@ -7775,7 +7862,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
|
||||
// Aniso filtering doesn't work with textureLod so use texture (automatic_lod) instead.
|
||||
// Enable aniso only for triangles. Sprites are flat so aniso is likely useless (it would save perf for others primitives).
|
||||
const bool anisotropic = m_vt.m_primclass == GS_TRIANGLE_CLASS && !trilinear_manual;
|
||||
m_conf.sampler.aniso = anisotropic;
|
||||
m_conf.ps.sw_aniso = anisotropic ? GSConfig.MaxAnisotropy : 0;
|
||||
m_conf.sampler.triln = trilinear;
|
||||
if (anisotropic && !trilinear_manual)
|
||||
m_conf.ps.automatic_lod = 1;
|
||||
@@ -7805,7 +7892,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
|
||||
{
|
||||
m_conf.tex = nullptr;
|
||||
m_conf.ps.tex_is_fb = true;
|
||||
if (m_prim_overlap == PRIM_OVERLAP_NO || !(g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
|
||||
if (m_prim_overlap == PRIM_OVERLAP_NO || !g_gs_device->Features().feedback_loops())
|
||||
m_conf.require_one_barrier = true;
|
||||
else
|
||||
m_conf.require_full_barrier = true;
|
||||
@@ -7885,7 +7972,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c
|
||||
const int horizontal_offset = ((page_offset % src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.x);
|
||||
const int vertical_offset = ((page_offset / src_target->m_TEX0.TBW) * GSLocalMemory::m_psm[src_target->m_TEX0.PSM].pgs.y);
|
||||
|
||||
if (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy)
|
||||
if (g_gs_device->Features().feedback_loops())
|
||||
{
|
||||
const u32 max_skip = ((m_channel_shuffle_finish || !m_channel_shuffle_width) ? 1 : m_channel_shuffle_width) << 5;
|
||||
const bool new_shuffle = !(m_last_channel_shuffle_fbmsk == m_context->FRAME.FBMSK &&
|
||||
@@ -8109,7 +8196,7 @@ bool GSRendererHW::CanUseTexIsFB(const GSTextureCache::Target* rt, const GSTextu
|
||||
}
|
||||
|
||||
// No barriers -> we can't use tex-is-fb when there's overlap.
|
||||
if (!(g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy) && m_prim_overlap != PRIM_OVERLAP_NO)
|
||||
if (!g_gs_device->Features().feedback_loops() && m_prim_overlap != PRIM_OVERLAP_NO)
|
||||
{
|
||||
GL_CACHE("HW: Disabling tex-is-fb due to no barriers.");
|
||||
return false;
|
||||
@@ -8351,19 +8438,18 @@ void GSRendererHW::EmulateAlphaTest(DATEOptions& date_options)
|
||||
|
||||
// Determine whether the feedback methods require a single pass.
|
||||
const bool feedback_one_pass = simple_fb_only || simple_rgb_only || simple_zb_only;
|
||||
|
||||
// If we already have the required barriers for the accurate feedback path and
|
||||
// do not require depth feedback.
|
||||
|
||||
// If we already have the required barriers for the accurate feedback path.
|
||||
const bool free_barrier_feedback =
|
||||
((m_conf.require_one_barrier && feedback_one_pass) || m_conf.require_full_barrier) &&
|
||||
(features.texture_barrier || features.multidraw_fb_copy) &&
|
||||
!afail_needs_depth;
|
||||
features.feedback_loops() &&
|
||||
(!afail_needs_depth || m_conf.ps.IsFeedbackLoopDepth());
|
||||
|
||||
// Determine if we can use FB-fetch for color only feedback.
|
||||
const bool free_fbfetch_feedback = features.framebuffer_fetch && !afail_needs_depth;
|
||||
|
||||
// Determine if we have the correct features for depth feedback.
|
||||
const bool depth_feedback_supported = features.depth_feedback != GSDevice::DepthFeedbackSupport::None;
|
||||
const bool depth_feedback_supported = features.feedback_loops();
|
||||
|
||||
// We need depth feedback but do not have the correct features.
|
||||
const bool avoid_feedback = afail_needs_depth && !depth_feedback_supported;
|
||||
@@ -8386,26 +8472,19 @@ void GSRendererHW::EmulateAlphaTest(DATEOptions& date_options)
|
||||
m_conf.cb_ps.FogColor_AREF.a = ps_aref;
|
||||
m_conf.ps.afail = static_cast<PS_AFAIL>(afail);
|
||||
|
||||
if ((afail_needs_rt || afail_needs_depth) && (features.texture_barrier || features.multidraw_fb_copy))
|
||||
if ((afail_needs_rt || afail_needs_depth) && features.feedback_loops())
|
||||
{
|
||||
m_conf.require_one_barrier |= feedback_one_pass;
|
||||
m_conf.require_full_barrier |= !feedback_one_pass;
|
||||
}
|
||||
|
||||
// Handle SW depth writing and/or testing.
|
||||
if (afail_needs_depth)
|
||||
{
|
||||
pxAssert(zwe);
|
||||
GL_INS("Enable SW depth write for depth feedback");
|
||||
|
||||
// Let the pipeline know we need depth feedback with RGB_ONLY.
|
||||
if (afail == AFAIL_RGB_ONLY)
|
||||
m_conf.ps.afail = PS_AFAIL::RGB_ONLY_SW_Z;
|
||||
|
||||
if (m_cached_ctx.DepthRead())
|
||||
{
|
||||
GL_INS("Enable SW depth testing for depth feedback");
|
||||
m_conf.ps.ztst = m_cached_ctx.TEST.ZTST; // Enable SW Z test.
|
||||
m_conf.depth.ztst = ZTST_ALWAYS; // Disable HW Z test.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -8413,6 +8492,8 @@ void GSRendererHW::EmulateAlphaTest(DATEOptions& date_options)
|
||||
pxAssert(afail != AFAIL_FB_ONLY);
|
||||
}
|
||||
|
||||
ConfigureDepthFeedback(); // Enable barriers/SW depth test if needed.
|
||||
|
||||
m_conf.alpha_test = GSHWDrawConfig::AlphaTestMode::FEEDBACK;
|
||||
}
|
||||
else if (simple_fb_only)
|
||||
@@ -8568,11 +8649,21 @@ void GSRendererHW::EmulateAlphaTestSecondPass()
|
||||
"Alpha second pass has no color/depth write.");
|
||||
}
|
||||
|
||||
// Some housekeeping for the first pass.
|
||||
if (!m_conf.depth.zwe)
|
||||
{
|
||||
m_conf.ps.DisableDepthOutput();
|
||||
}
|
||||
|
||||
// Some housekeeping for the second pass.
|
||||
if (m_conf.alpha_second_pass.colormask.wrgba == 0)
|
||||
{
|
||||
m_conf.alpha_second_pass.ps.DisableColorOutput();
|
||||
}
|
||||
if (!m_conf.alpha_second_pass.depth.zwe)
|
||||
{
|
||||
m_conf.alpha_second_pass.ps.DisableDepthOutput();
|
||||
}
|
||||
if (m_conf.alpha_second_pass.ps.IsFeedbackLoopRT() || m_conf.alpha_second_pass.ps.IsFeedbackLoopDepth())
|
||||
{
|
||||
m_conf.alpha_second_pass.require_one_barrier = m_conf.require_one_barrier;
|
||||
@@ -8594,6 +8685,30 @@ void GSRendererHW::EmulateAlphaTestSecondPass()
|
||||
m_conf.alpha_test = GSHWDrawConfig::AlphaTestMode::ABORT_DRAW;
|
||||
}
|
||||
|
||||
// Setup barriers and/or SW depth testing for depth feedback.
|
||||
void GSRendererHW::ConfigureDepthFeedback()
|
||||
{
|
||||
if (m_conf.ps.IsFeedbackLoopDepth())
|
||||
{
|
||||
const GSDevice::FeatureSupport& features = g_gs_device->Features();
|
||||
|
||||
// We need barriers for the feedback.
|
||||
if (features.feedback_loops())
|
||||
{
|
||||
m_conf.require_one_barrier |= (m_prim_overlap == PRIM_OVERLAP_NO);
|
||||
m_conf.require_full_barrier |= (m_prim_overlap != PRIM_OVERLAP_NO);
|
||||
}
|
||||
|
||||
// We need to do SW depth testing if it's used.
|
||||
if (m_cached_ctx.DepthRead())
|
||||
{
|
||||
GL_INS("HW: Enable SW depth test and disable HW.");
|
||||
m_conf.ps.ztst = m_cached_ctx.TEST.ZTST; // Enable SW Z test.
|
||||
m_conf.depth.ztst = ZTST_ALWAYS; // Disable HW Z test.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GSRendererHW::CleanupDraw(bool invalidate_temp_src)
|
||||
{
|
||||
// Remove any RT source.
|
||||
@@ -8660,6 +8775,9 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
|
||||
m_prim_overlap = PrimitiveOverlap(false);
|
||||
|
||||
// Do AA1 setup early so we can mask depth if possible.
|
||||
EmulateAA1();
|
||||
|
||||
if (rt)
|
||||
{
|
||||
EmulateTextureShuffleAndFbmask(rt, tex);
|
||||
@@ -8689,7 +8807,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
EmulateDATESelectMethod(date_options, rt, blend_alpha_min, blend_alpha_max);
|
||||
|
||||
// Before emulateblending, dither will be used
|
||||
m_conf.ps.dither = GSConfig.Dithering > 0 && m_conf.ps.dst_fmt == GSLocalMemory::PSM_FMT_16 && env.DTHE.DTHE;
|
||||
m_conf.ps.dither = GSConfig.Dithering > 0 && m_conf.ps.dst_fmt == GSLocalMemory::PSM_FMT_16 && !!env.DTHE.DTHE;
|
||||
|
||||
if (m_conf.ps.dst_fmt == GSLocalMemory::PSM_FMT_24)
|
||||
{
|
||||
@@ -8726,8 +8844,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
// Perform alpha test first pass setup here as bending depends on it.
|
||||
EmulateAlphaTest(date_options);
|
||||
|
||||
// AA1: Set alpha source to coverage 128 when there is no alpha blending.
|
||||
m_conf.ps.fixed_one_a = IsCoverageAlpha();
|
||||
// AA1: Set alpha source to coverage 128 when AA1 is not supported.
|
||||
m_conf.ps.fixed_one_a = IsCoverageAlphaFixedOne();
|
||||
|
||||
if ((!IsOpaque() || m_context->ALPHA.IsBlack()) && rt && ((m_conf.colormask.wrgba & 0x7) || (m_texture_shuffle && !m_texture_shuffle.real_16_bit_source && !m_texture_shuffle.SameGroupShuffle())))
|
||||
{
|
||||
@@ -8750,7 +8868,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
}
|
||||
|
||||
// Similar to IsRTWritten(), check if the rt will change.
|
||||
const bool no_rt = !rt || !(m_conf.colormask.wrgba || m_channel_shuffle);
|
||||
const bool no_rt = !rt || m_conf.colormask.wrgba == 0;
|
||||
const bool no_ds = !ds ||
|
||||
// Depth will be written through the RT.
|
||||
(!no_rt && m_cached_ctx.FRAME.FBP == m_cached_ctx.ZBUF.ZBP && !PRIM->TME && m_cached_ctx.ZBUF.ZMSK == 0 &&
|
||||
@@ -8843,9 +8961,24 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
|
||||
HandleProvokingVertexFirst();
|
||||
|
||||
SetupIA(rtscale, vs_scale_x, vs_scale_y, m_channel_shuffle_width != 0);
|
||||
SetupIA(rtscale, vs_scale_x, vs_scale_y, m_channel_shuffle_width != 0, no_rt);
|
||||
|
||||
StartDepthAsRTFeedback(); // Depends on the drawarea and alpha test having been determined.
|
||||
if (m_conf.ds && m_conf.ps.IsFeedbackLoopDepth() && !g_gs_device->Features().depth_feedback)
|
||||
{
|
||||
GL_PUSH("HW: Creating temporary R32 RT for depth feedback");
|
||||
|
||||
// Should not be hw blending with multiple render targets.
|
||||
pxAssert(!m_conf.blend.enable && !m_conf.blend_multi_pass.blend.enable);
|
||||
// We should have depth output or feedback doesn't make sense.
|
||||
// We will output to both the depth buffer and color clone simultaneously in the shader.
|
||||
pxAssert(m_conf.depth.zwe);
|
||||
// HW depth test should be disabled in place of SW depth test
|
||||
pxAssert(m_conf.depth.ztst == ZTST_ALWAYS);
|
||||
// Second pass alpha shouldn't be enabled
|
||||
pxAssert(!m_conf.alpha_second_pass.enable);
|
||||
|
||||
g_gs_device->BeginDSAsRT(m_conf.ds, m_conf.drawarea);
|
||||
}
|
||||
|
||||
if (GSConfig.SaveHWConfig && GSConfig.ShouldDump(s_n, g_perfmon.GetFrame()))
|
||||
{
|
||||
@@ -8857,7 +8990,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
|
||||
else
|
||||
m_last_rt = rt;
|
||||
|
||||
CleanupDepthAsRTFeedback();
|
||||
if (g_gs_device->IsDSInRTActive())
|
||||
g_gs_device->EndDSAsRT();
|
||||
}
|
||||
|
||||
// If the EE uploaded a new CLUT since the last draw, use that.
|
||||
@@ -9630,12 +9764,12 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
|
||||
rt_end_bp, m_cached_ctx.FRAME.PSM, m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.FBMSK);
|
||||
|
||||
GSUploadQueue clear_queue;
|
||||
clear_queue.transfer_type = EEGS_TransferType::Clear;
|
||||
clear_queue.draw = s_n;
|
||||
clear_queue.rect = m_r;
|
||||
clear_queue.blit.DBP = m_cached_ctx.FRAME.Block();
|
||||
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
|
||||
clear_queue.blit.DPSM = m_cached_ctx.FRAME.PSM;
|
||||
clear_queue.zero_clear = true;
|
||||
m_draw_transfers.push_back(clear_queue);
|
||||
}
|
||||
else
|
||||
@@ -9660,12 +9794,12 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
|
||||
ds_end_bp, m_cached_ctx.ZBUF.PSM, m_cached_ctx.FRAME.FBW);
|
||||
|
||||
GSUploadQueue clear_queue;
|
||||
clear_queue.transfer_type = EEGS_TransferType::Clear;
|
||||
clear_queue.draw = s_n;
|
||||
clear_queue.rect = m_r;
|
||||
clear_queue.blit.DBP = m_cached_ctx.ZBUF.Block();
|
||||
clear_queue.blit.DBW = m_cached_ctx.FRAME.FBW;
|
||||
clear_queue.blit.DPSM = m_cached_ctx.ZBUF.PSM;
|
||||
clear_queue.zero_clear = true;
|
||||
m_draw_transfers.push_back(clear_queue);
|
||||
}
|
||||
}
|
||||
@@ -10375,45 +10509,7 @@ std::size_t GSRendererHW::ComputeDrawlistGetSize(float scale)
|
||||
return m_drawlist.size();
|
||||
}
|
||||
|
||||
void GSRendererHW::StartDepthAsRTFeedback()
|
||||
bool GSRendererHW::IsCoverageAlphaSupported()
|
||||
{
|
||||
// Create a temporary depth color target
|
||||
if (m_conf.ds && m_conf.ps.IsFeedbackLoopDepth() &&
|
||||
g_gs_device->Features().depth_feedback == GSDevice::DepthFeedbackSupport::DepthAsRT)
|
||||
{
|
||||
GL_PUSH("HW: Creating temporary R32 RT for depth feedback");
|
||||
|
||||
// Should not be hw blending with multiple render targets.
|
||||
pxAssert(!m_conf.blend.enable && !m_conf.blend_multi_pass.blend.enable);
|
||||
|
||||
// We should have depth output or feedback doesn't make sense.
|
||||
// We will output to both the depth buffer and color clone simultaneously in the shader.
|
||||
pxAssert(m_conf.depth.zwe);
|
||||
|
||||
// Disable HW depth test since we will be using SW depth test (if needed).
|
||||
m_conf.depth.ztst = ZTST_ALWAYS;
|
||||
if (m_conf.alpha_second_pass.enable && m_conf.alpha_second_pass.depth.zwe)
|
||||
{
|
||||
// Do the same with the alpha second pass.
|
||||
m_conf.alpha_second_pass.depth.ztst = ZTST_ALWAYS;
|
||||
}
|
||||
|
||||
// Create a temporary RT and copy the area needed for the draw.
|
||||
const int w = m_conf.ds->GetWidth();
|
||||
const int h = m_conf.ds->GetHeight();
|
||||
m_conf.ds_as_rt = g_gs_device->CreateRenderTarget(w, h, GSTexture::Format::Float32, false, true);
|
||||
const GSVector4 dRect(m_conf.drawarea);
|
||||
const GSVector4 sRect(dRect.x / w, dRect.y / h, dRect.z / w, dRect.w / h);
|
||||
g_gs_device->StretchRect(m_conf.ds, sRect, m_conf.ds_as_rt, dRect, ShaderConvert::FLOAT32_DEPTH_TO_COLOR, false);
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSRendererHW::CleanupDepthAsRTFeedback()
|
||||
{
|
||||
if (m_conf.ds_as_rt)
|
||||
{
|
||||
g_gs_device->Recycle(m_conf.ds_as_rt);
|
||||
m_conf.ds_as_rt = nullptr;
|
||||
}
|
||||
return IsCoverageAlpha() && IsRTWritten() && g_gs_device->Features().aa1;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1340,6 +1340,14 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Invalid values show up in dumps but it's unclear if it's a core issue or a bug in the games.
|
||||
// Return a temporary source to prevent the lookup from corrupting the TC.
|
||||
if (TEX0.TBW > 32 && !GSUtil::IsValidPSM(static_cast<int>(TEX0.PSM)))
|
||||
{
|
||||
GL_CACHE("TC: Invalid TEX0 (TBW=%d, PSM=%x), using temporary source.", TEX0.TBW, TEX0.PSM);
|
||||
return CreateSource(TEX0, TEXA, CLAMP, nullptr, 0, 0, nullptr, nullptr, nullptr, SourceRegion(), true);
|
||||
}
|
||||
|
||||
const GSVector2i compare_lod(lod ? *lod : GSVector2i(0, 0));
|
||||
Source* src = nullptr;
|
||||
|
||||
@@ -1432,7 +1440,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
// Transfers completely outside the valid area may not have been given a dirty rect, so we need both checks.
|
||||
const bool t_maybe_dirty = !t->m_dirty.empty() || !t->OverlapsValid(bp, bw, psm, req_rect);
|
||||
|
||||
if ((bp == t->m_TEX0.TBP0 && t_maybe_dirty && GSUtil::GetChannelMask(psm) == GSUtil::GetChannelMask(t->m_TEX0.PSM) && GSRendererHW::GetInstance()->m_draw_transfers.size() > 0) || GSState::s_n==421)
|
||||
if (bp == t->m_TEX0.TBP0 && t_maybe_dirty && GSUtil::GetChannelMask(psm) == GSUtil::GetChannelMask(t->m_TEX0.PSM) && GSRendererHW::GetInstance()->m_draw_transfers.size() > 0)
|
||||
{
|
||||
bool can_use = true;
|
||||
|
||||
@@ -2253,20 +2261,34 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const bool is_color, const
|
||||
}
|
||||
}
|
||||
|
||||
if (src->m_from_target && src->m_target_direct && src->m_region.HasEither())
|
||||
if (src->m_from_target && src->m_target_direct)
|
||||
{
|
||||
if (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0)
|
||||
GSVector4i req_rect = r;
|
||||
|
||||
// The read area might be offset but the start of the texture is at the beginning of the space.
|
||||
req_rect.x = region.HasX() ? region.GetMinX() : 0;
|
||||
req_rect.y = region.HasY() ? region.GetMinY() : 0;
|
||||
|
||||
const bool dirty_overlap = !src->m_from_target->m_dirty.GetTotalRect(src->m_from_target->m_TEX0, src->m_from_target->m_unscaled_size).rintersect(req_rect).rempty();
|
||||
|
||||
if (dirty_overlap)
|
||||
src->m_from_target->Update();
|
||||
|
||||
if (src->m_region.HasEither())
|
||||
{
|
||||
src->m_region.bits = 0;
|
||||
src->m_region.SetX(0, region.HasX() ? region.GetMaxX() : (1 << TEX0.TW));
|
||||
src->m_region.SetY(0, region.HasY() ? region.GetMaxY() : (1 << TEX0.TH));
|
||||
}
|
||||
else if (src->m_TEX0.TBP0 > src->m_from_target->m_TEX0.TBP0)
|
||||
{
|
||||
GSVector4i dst_offset = TranslateAlignedRectByPage(src->m_from_target, src->m_TEX0.TBP0, src->m_TEX0.PSM, src->m_TEX0.TBW, GSVector4i(0, 0, 1, 1), false);
|
||||
src->m_region.bits = 0;
|
||||
src->m_region.SetX(dst_offset.x, dst_offset.x + (region.HasX() ? std::min(region.GetMaxX(), (1 << TEX0.TW)) : (1 << TEX0.TW)));
|
||||
src->m_region.SetY(dst_offset.y, dst_offset.y + (region.HasY() ? std::min(region.GetMaxY(), (1 << TEX0.TH)) : (1 << TEX0.TH)));
|
||||
if (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0)
|
||||
{
|
||||
src->m_region.bits = 0;
|
||||
src->m_region.SetX(0, region.HasX() ? region.GetMaxX() : (1 << TEX0.TW));
|
||||
src->m_region.SetY(0, region.HasY() ? region.GetMaxY() : (1 << TEX0.TH));
|
||||
}
|
||||
else if (src->m_TEX0.TBP0 > src->m_from_target->m_TEX0.TBP0)
|
||||
{
|
||||
GSVector4i dst_offset = TranslateAlignedRectByPage(src->m_from_target, src->m_TEX0.TBP0, src->m_TEX0.PSM, src->m_TEX0.TBW, GSVector4i(0, 0, 1, 1), false);
|
||||
src->m_region.bits = 0;
|
||||
src->m_region.SetX(dst_offset.x, dst_offset.x + (region.HasX() ? std::min(region.GetMaxX(), (1 << TEX0.TW)) : (1 << TEX0.TW)));
|
||||
src->m_region.SetY(dst_offset.y, dst_offset.y + (region.HasY() ? std::min(region.GetMaxY(), (1 << TEX0.TH)) : (1 << TEX0.TH)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2341,7 +2363,7 @@ void GSTextureCache::CombineAlignedInsideTargets(Target* target, GSTextureCache:
|
||||
const GSVector4 target_drect = GSVector4(target_drect_unscaled) * target->m_scale;
|
||||
|
||||
const bool valid_color = t->m_valid_rgb;
|
||||
const bool valid_alpha = (t->m_valid_alpha_high | t->m_valid_alpha_low) && (GSUtil::GetChannelMask(t->m_TEX0.PSM) & 0x8);
|
||||
const bool valid_alpha = (t->m_valid_alpha_high || t->m_valid_alpha_low) && (GSUtil::GetChannelMask(t->m_TEX0.PSM) & 0x8);
|
||||
|
||||
target->m_valid_alpha_high |= t->m_valid_alpha_high;
|
||||
target->m_valid_alpha_low |= t->m_valid_alpha_low;
|
||||
@@ -3474,14 +3496,15 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
else
|
||||
eerect = eerect.runion(targetr);
|
||||
|
||||
const bool transfer_is_clear = iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear;
|
||||
// Later writes might be partial over a previously cleared area. We want to upload in these cases.
|
||||
hw_clear = hw_clear.has_value() ? (hw_clear.value() && iter->zero_clear) : iter->zero_clear;
|
||||
hw_clear = hw_clear.has_value() ? (hw_clear.value() && transfer_is_clear) : transfer_is_clear;
|
||||
|
||||
// When the write covers the entire target, don't bother checking any earlier writes.
|
||||
if (iter->blit.DBP <= TEX0.TBP0 && transfer_end >= rect_end)
|
||||
{
|
||||
// If it was a clear draw then we can use that as our target size.
|
||||
if (iter->zero_clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
|
||||
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DPSM == TEX0.PSM)
|
||||
dst->UpdateValidity(iter->rect);
|
||||
|
||||
// Some games clear RT and Z at the same time, only erase if it's specifically this target.
|
||||
@@ -3520,7 +3543,7 @@ bool GSTextureCache::PreloadTarget(GIFRegTEX0 TEX0, const GSVector2i& size, cons
|
||||
if (iter->draw < (GSState::s_n - 1))
|
||||
break;
|
||||
|
||||
if (iter->zero_clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW)
|
||||
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->blit.DBP == TEX0.TBP0 && iter->blit.DBW == TEX0.TBW)
|
||||
{
|
||||
hw_clear = true;
|
||||
transfers.erase(std::next(iter).base());
|
||||
@@ -3855,7 +3878,6 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con
|
||||
const u32 bp = TEX0.TBP0;
|
||||
|
||||
Target* dst = nullptr;
|
||||
Target* dst_match = nullptr;
|
||||
auto* list = &m_dst[RenderTarget];
|
||||
|
||||
// Let's try to find a perfect frame that contains valid data
|
||||
@@ -4011,7 +4033,7 @@ GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(GIFRegTEX0 TEX0, con
|
||||
else
|
||||
eerect = eerect.runion(targetr);
|
||||
|
||||
if (iter->zero_clear && iter->draw == last_draw)
|
||||
if (iter->transfer_type == GSRendererHW::GetInstance()->EEGS_TransferType::Clear && iter->draw == last_draw)
|
||||
{
|
||||
can_create = false;
|
||||
break;
|
||||
|
||||
@@ -263,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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -95,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)
|
||||
@@ -245,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
|
||||
@@ -388,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
|
||||
@@ -449,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)
|
||||
@@ -474,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]);
|
||||
@@ -485,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!");
|
||||
}
|
||||
|
||||
@@ -838,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];
|
||||
@@ -849,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))
|
||||
@@ -935,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"];
|
||||
@@ -976,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
|
||||
@@ -1055,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
|
||||
@@ -1266,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;
|
||||
}}
|
||||
|
||||
@@ -1757,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)
|
||||
@@ -1800,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;
|
||||
@@ -1842,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);
|
||||
@@ -1886,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)));
|
||||
@@ -1920,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);
|
||||
|
||||
@@ -1973,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
|
||||
@@ -1984,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())
|
||||
@@ -2123,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)
|
||||
@@ -2131,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);
|
||||
@@ -2266,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))
|
||||
@@ -2274,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)
|
||||
@@ -2306,7 +2443,7 @@ void GSDeviceMTL::RenderHW(GSHWDrawConfig& config)
|
||||
|
||||
Recycle(colclip_rt);
|
||||
|
||||
g_gs_device->SetColorClipTexture(nullptr);
|
||||
SetColorClipTexture(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2314,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;
|
||||
@@ -2326,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;
|
||||
}
|
||||
|
||||
@@ -2366,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;
|
||||
}
|
||||
|
||||
@@ -2386,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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
// ****************************************************************
|
||||
@@ -830,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)
|
||||
@@ -893,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;
|
||||
}
|
||||
@@ -964,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)
|
||||
@@ -1179,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);
|
||||
@@ -1335,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;
|
||||
}
|
||||
|
||||
@@ -1438,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
|
||||
@@ -1561,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);
|
||||
@@ -2118,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)
|
||||
@@ -2137,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)
|
||||
@@ -2565,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);
|
||||
@@ -2693,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:
|
||||
@@ -2708,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;
|
||||
@@ -2720,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);
|
||||
@@ -2749,22 +2803,15 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
|
||||
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;
|
||||
@@ -2774,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.
|
||||
@@ -2824,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;
|
||||
@@ -2832,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.
|
||||
@@ -2852,7 +2899,7 @@ 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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -468,7 +468,25 @@ void GSRendererSW::Draw()
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -82,6 +82,7 @@ protected:
|
||||
template <u32 primclass>
|
||||
void RewriteVerticesIfSTOverflow();
|
||||
|
||||
bool IsCoverageAlphaSupported() override;
|
||||
public:
|
||||
GSRendererSW(int threads);
|
||||
~GSRendererSW() override;
|
||||
|
||||
@@ -461,7 +461,6 @@ bool GSDeviceVK::SelectDeviceFeatures()
|
||||
m_device_features.wideLines = available_features.wideLines;
|
||||
m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
|
||||
m_device_features.textureCompressionBC = available_features.textureCompressionBC;
|
||||
m_device_features.samplerAnisotropy = available_features.samplerAnisotropy;
|
||||
m_device_features.geometryShader = available_features.geometryShader;
|
||||
|
||||
return true;
|
||||
@@ -953,7 +952,7 @@ bool GSDeviceVK::CreateGlobalDescriptorPool()
|
||||
{
|
||||
static constexpr const VkDescriptorPoolSize pool_sizes[] = {
|
||||
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 2},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3},
|
||||
};
|
||||
|
||||
VkDescriptorPoolCreateInfo pool_create_info = {VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, nullptr,
|
||||
@@ -1421,6 +1420,9 @@ void GSDeviceVK::ExecuteCommandBuffer(WaitType wait_for_completion)
|
||||
}
|
||||
WaitForCommandBufferCompletion(current_frame);
|
||||
}
|
||||
|
||||
// Push constants need to be refreshed each command buffer.
|
||||
m_dirty_flags |= DIRTY_FLAG_VS_PUSH_CONSTANTS;
|
||||
}
|
||||
|
||||
void GSDeviceVK::DeferBufferDestruction(VkBuffer object, VmaAllocation allocation)
|
||||
@@ -2699,15 +2701,8 @@ bool GSDeviceVK::CheckFeatures()
|
||||
m_features.line_expand =
|
||||
(m_device_features.wideLines && limits.lineWidthRange[0] <= f_upscale && limits.lineWidthRange[1] >= f_upscale);
|
||||
|
||||
if (m_features.texture_barrier && (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 = m_features.feedback_loops();
|
||||
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
|
||||
|
||||
DevCon.WriteLn("Optional features:%s%s%s%s%s", m_features.primitive_id ? " primitive_id" : "",
|
||||
m_features.texture_barrier ? " texture_barrier" : "", m_features.framebuffer_fetch ? " framebuffer_fetch" : "",
|
||||
@@ -2761,8 +2756,7 @@ void GSDeviceVK::DrawPrimitive()
|
||||
|
||||
void GSDeviceVK::DrawIndexedPrimitive()
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
|
||||
vkCmdDrawIndexed(GetCurrentCommandBuffer(), m_index.count, 1, m_index.start, m_vertex.start, 0);
|
||||
DrawIndexedPrimitive(0, m_index.count);
|
||||
}
|
||||
|
||||
void GSDeviceVK::DrawIndexedPrimitive(int offset, int count)
|
||||
@@ -2772,6 +2766,42 @@ void GSDeviceVK::DrawIndexedPrimitive(int offset, int count)
|
||||
vkCmdDrawIndexed(GetCurrentCommandBuffer(), count, 1, m_index.start + offset, m_vertex.start, 0);
|
||||
}
|
||||
|
||||
void GSDeviceVK::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);
|
||||
if (vs_indexing)
|
||||
{
|
||||
SetVSPushConstants(m_vertex.start, m_index.start + offset);
|
||||
vkCmdDraw(GetCurrentCommandBuffer(), count * vs_indexing_expansion, 1, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetVSPushConstants(m_vertex.start);
|
||||
vkCmdDrawIndexed(GetCurrentCommandBuffer(), count, 1, m_index.start + offset, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceVK::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 GSDeviceVK::Draw(const GSHWDrawConfig& config)
|
||||
{
|
||||
Draw(config, 0, m_index.count);
|
||||
}
|
||||
|
||||
VkFormat GSDeviceVK::LookupNativeFormat(GSTexture::Format format) const
|
||||
{
|
||||
static constexpr std::array<VkFormat, static_cast<int>(GSTexture::Format::Last) + 1> s_format_mapping = {{
|
||||
@@ -3427,25 +3457,35 @@ void GSDeviceVK::IASetVertexBuffer(const void* vertex, size_t stride, size_t cou
|
||||
m_vertex_stream_buffer.CommitMemory(size);
|
||||
}
|
||||
|
||||
void GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
|
||||
void GSDeviceVK::UploadIndices(VKStreamBuffer& 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)))
|
||||
{
|
||||
ExecuteCommandBufferAndRestartRenderPass(false, "Uploading bytes to index buffer");
|
||||
if (!m_index_stream_buffer.ReserveMemory(size, sizeof(u16)))
|
||||
if (!buffer.ReserveMemory(size, sizeof(u16)))
|
||||
pxFailRel("Failed to reserve space for vertices");
|
||||
}
|
||||
|
||||
m_index.start = m_index_stream_buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.start = buffer.GetCurrentOffset() / sizeof(u16);
|
||||
m_index.count = count;
|
||||
|
||||
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 GSDeviceVK::IASetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
UploadIndices(m_index_stream_buffer, index, count);
|
||||
|
||||
SetIndexBuffer(m_index_stream_buffer.GetBuffer());
|
||||
}
|
||||
|
||||
void GSDeviceVK::VSSetIndexBuffer(const void* index, size_t count)
|
||||
{
|
||||
UploadIndices(m_expand_index_stream_buffer, index, count);
|
||||
}
|
||||
|
||||
void GSDeviceVK::OMSetRenderTargets(
|
||||
GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, FeedbackLoopFlag feedback_loop)
|
||||
{
|
||||
@@ -3623,8 +3663,6 @@ VkSampler GSDeviceVK::GetSampler(GSHWDrawConfig::SamplerSelector ss)
|
||||
if (it != m_samplers.end())
|
||||
return it->second;
|
||||
|
||||
const bool aniso = (ss.aniso && GSConfig.MaxAnisotropy > 1 && m_device_features.samplerAnisotropy);
|
||||
|
||||
// See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSamplerCreateInfo.html#_description
|
||||
// for the reasoning behind 0.25f here.
|
||||
const VkSamplerCreateInfo ci = {
|
||||
@@ -3638,8 +3676,8 @@ VkSampler GSDeviceVK::GetSampler(GSHWDrawConfig::SamplerSelector ss)
|
||||
ss.tav ? VK_SAMPLER_ADDRESS_MODE_REPEAT : VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE), // v
|
||||
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // w
|
||||
0.0f, // lod bias
|
||||
static_cast<VkBool32>(aniso), // anisotropy enable
|
||||
aniso ? static_cast<float>(GSConfig.MaxAnisotropy) : 1.0f, // anisotropy
|
||||
VK_FALSE, // anisotropy enable
|
||||
1.0f, // anisotropy
|
||||
VK_FALSE, // compare enable
|
||||
VK_COMPARE_OP_ALWAYS, // compare op
|
||||
0.0f, // min lod
|
||||
@@ -3775,6 +3813,12 @@ bool GSDeviceVK::CreateBuffers()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_expand_index_stream_buffer.Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, m_features.aa1 ? INDEX_BUFFER_SIZE : 4))
|
||||
{
|
||||
Host::ReportErrorAsync("GS", "Failed to allocate expansion index buffer (VS resource)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_vertex_uniform_stream_buffer.Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VERTEX_UNIFORM_BUFFER_SIZE))
|
||||
{
|
||||
Host::ReportErrorAsync("GS", "Failed to allocate vertex uniform buffer");
|
||||
@@ -3833,7 +3877,12 @@ bool GSDeviceVK::CreatePipelineLayouts()
|
||||
0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_GEOMETRY_BIT);
|
||||
dslb.AddBinding(1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
if (m_features.vs_expand)
|
||||
{
|
||||
dslb.AddBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
plb.AddPushConstants(VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GSHWDrawConfig::VSPushConstants));
|
||||
}
|
||||
if (m_features.aa1)
|
||||
dslb.AddBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
if ((m_tfx_ubo_ds_layout = dslb.Create(dev)) == VK_NULL_HANDLE)
|
||||
return false;
|
||||
Vulkan::SetObjectName(dev, m_tfx_ubo_ds_layout, "TFX UBO descriptor layout");
|
||||
@@ -4713,6 +4762,7 @@ void GSDeviceVK::DestroyResources()
|
||||
m_vertex_uniform_stream_buffer.Destroy(false);
|
||||
m_index_stream_buffer.Destroy(false);
|
||||
m_vertex_stream_buffer.Destroy(false);
|
||||
m_expand_index_stream_buffer.Destroy(false);
|
||||
if (m_expand_index_buffer != VK_NULL_HANDLE)
|
||||
vmaDestroyBuffer(m_allocator, m_expand_index_buffer, m_expand_index_buffer_allocation);
|
||||
|
||||
@@ -4855,6 +4905,10 @@ VkShaderModule GSDeviceVK::GetTFXFragmentShader(const GSHWDrawConfig::PSSelector
|
||||
AddMacro(ss, "PS_NO_COLOR", sel.no_color);
|
||||
AddMacro(ss, "PS_NO_COLOR1", sel.no_color1);
|
||||
AddMacro(ss, "PS_ZTST", sel.ztst);
|
||||
AddMacro(ss, "PS_AA1", static_cast<u32>(sel.aa1));
|
||||
AddMacro(ss, "PS_ABE", sel.abe);
|
||||
AddMacro(ss, "PS_ANISOTROPIC_FILTERING", sel.sw_aniso);
|
||||
|
||||
ss << m_tfx_source;
|
||||
|
||||
VkShaderModule mod = g_vulkan_shader_cache->GetFragmentShader(ss.str());
|
||||
@@ -5056,6 +5110,11 @@ bool GSDeviceVK::CreatePersistentDescriptorSets()
|
||||
dsub.AddBufferDescriptorWrite(m_tfx_ubo_descriptor_set, 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
m_vertex_stream_buffer.GetBuffer(), 0, VERTEX_BUFFER_SIZE);
|
||||
}
|
||||
if (m_features.aa1)
|
||||
{
|
||||
dsub.AddBufferDescriptorWrite(m_tfx_ubo_descriptor_set, 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
m_expand_index_stream_buffer.GetBuffer(), 0, INDEX_BUFFER_SIZE);
|
||||
}
|
||||
dsub.Update(dev);
|
||||
Vulkan::SetObjectName(dev, m_tfx_ubo_descriptor_set, "Persistent TFX UBO set");
|
||||
return true;
|
||||
@@ -5434,7 +5493,7 @@ bool GSDeviceVK::ApplyTFXState(bool already_execed)
|
||||
if (flags & DIRTY_FLAG_VS_CONSTANT_BUFFER)
|
||||
{
|
||||
if (!m_vertex_uniform_stream_buffer.ReserveMemory(
|
||||
sizeof(m_vs_cb_cache), static_cast<u32>(m_device_properties.limits.minUniformBufferOffsetAlignment)))
|
||||
sizeof(m_vs_cb_cache), static_cast<u32>(m_device_properties.limits.minUniformBufferOffsetAlignment)))
|
||||
{
|
||||
if (already_execed)
|
||||
{
|
||||
@@ -5477,7 +5536,7 @@ bool GSDeviceVK::ApplyTFXState(bool already_execed)
|
||||
if (m_current_pipeline_layout != PipelineLayout::TFX)
|
||||
{
|
||||
m_current_pipeline_layout = PipelineLayout::TFX;
|
||||
flags |= DIRTY_FLAG_TFX_UBO | DIRTY_FLAG_TFX_TEXTURES;
|
||||
flags |= DIRTY_FLAG_TFX_UBO | DIRTY_FLAG_TFX_TEXTURES | DIRTY_FLAG_VS_PUSH_CONSTANTS;
|
||||
|
||||
// Clear out the RT/DS binding if feedback loop isn't on, because it'll be in the wrong state and make
|
||||
// the validation layer cranky. Not a big deal since we need to write it anyway.
|
||||
@@ -5497,6 +5556,9 @@ bool GSDeviceVK::ApplyTFXState(bool already_execed)
|
||||
&m_tfx_ubo_descriptor_set, NUM_TFX_DYNAMIC_OFFSETS, m_tfx_dynamic_offsets.data());
|
||||
}
|
||||
|
||||
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_TFX_TEXTURES)
|
||||
{
|
||||
if (flags & DIRTY_FLAG_TFX_TEXTURE_TEX)
|
||||
@@ -5585,6 +5647,19 @@ void GSDeviceVK::SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb)
|
||||
m_dirty_flags |= DIRTY_FLAG_PS_CONSTANT_BUFFER;
|
||||
}
|
||||
|
||||
void GSDeviceVK::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)
|
||||
{
|
||||
vkCmdPushConstants(GetCurrentCommandBuffer(), m_tfx_pipeline_layout,
|
||||
VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pc), &pc);
|
||||
}
|
||||
}
|
||||
|
||||
void GSDeviceVK::SetupDATE(GSTexture* rt, GSTexture* ds, SetDATM datm, const GSVector4i& bbox)
|
||||
{
|
||||
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
|
||||
@@ -5687,7 +5762,7 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config)
|
||||
pipe.ps.no_color = false;
|
||||
pipe.ps.no_color1 = true;
|
||||
if (BindDrawPipeline(pipe))
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
|
||||
// image is initialized/prepass is done, so finish up and get ready to do the "real" draw
|
||||
EndRenderPass();
|
||||
@@ -5817,6 +5892,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
}
|
||||
|
||||
// Destination Alpha Setup
|
||||
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
|
||||
@@ -5826,7 +5902,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
case GSHWDrawConfig::DestinationAlphaMode::StencilOne: // setup is done below
|
||||
{
|
||||
// we only need to do the setup here if we don't have barriers, in which case do full DATE.
|
||||
if (!m_features.texture_barrier)
|
||||
if (!need_barrier)
|
||||
{
|
||||
SetupDATE(draw_rt, config.ds, config.datm, config.drawarea);
|
||||
config.destination_alpha = GSHWDrawConfig::DestinationAlphaMode::Stencil;
|
||||
@@ -5916,7 +5992,8 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
pipe.feedback_loop_flags |= m_current_framebuffer_feedback_loop;
|
||||
}
|
||||
|
||||
if (draw_rt && (config.require_one_barrier || (config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
|
||||
if (draw_rt && ((config.require_one_barrier && (config.ps.IsFeedbackLoopRT() || config.alpha_second_pass.ps.IsFeedbackLoopRT())) ||
|
||||
(config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
|
||||
{
|
||||
// Requires a copy of the RT.
|
||||
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true));
|
||||
@@ -6036,7 +6113,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
if (BindDrawPipeline(pipe))
|
||||
{
|
||||
// TODO: This probably should have barriers, in case we want to use it conditionally.
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6154,14 +6231,18 @@ void GSDeviceVK::UpdateHWPipelineSelector(GSHWDrawConfig& config, PipelineSelect
|
||||
void GSDeviceVK::UploadHWDrawVerticesAndIndices(GSHWDrawConfig& config)
|
||||
{
|
||||
IASetVertexBuffer(config.verts, sizeof(GSVertex), config.nverts, GetVertexAlignment(config.vs.expand));
|
||||
m_vertex.start *= GetExpansionFactor(config.vs.expand);
|
||||
|
||||
if (config.vs.UseExpandIndexBuffer())
|
||||
if (config.vs.UseFixedExpandIndexBuffer())
|
||||
{
|
||||
m_index.start = 0;
|
||||
m_index.count = config.nindices;
|
||||
SetIndexBuffer(m_expand_index_buffer);
|
||||
}
|
||||
else if (config.vs.UseVSExpandIndexBuffer())
|
||||
{
|
||||
VSSetIndexBuffer(config.indices, config.nindices);
|
||||
SetVSConstantBuffer(config.cb_vs);
|
||||
}
|
||||
else
|
||||
{
|
||||
IASetIndexBuffer(config.indices, config.nindices);
|
||||
@@ -6202,7 +6283,7 @@ void GSDeviceVK::SendHWDraw(const GSHWDrawConfig& config, GSTextureVK* draw_rt,
|
||||
{
|
||||
if (!m_features.texture_barrier) [[unlikely]]
|
||||
{
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6258,7 +6339,7 @@ void GSDeviceVK::SendHWDraw(const GSHWDrawConfig& config, GSTextureVK* draw_rt,
|
||||
IssueBarriers();
|
||||
|
||||
const u32 count = (*config.drawlist)[n] * indices_per_prim;
|
||||
DrawIndexedPrimitive(p, count);
|
||||
Draw(config, p, count);
|
||||
p += count;
|
||||
}
|
||||
|
||||
@@ -6271,5 +6352,5 @@ void GSDeviceVK::SendHWDraw(const GSHWDrawConfig& config, GSTextureVK* draw_rt,
|
||||
IssueBarriers();
|
||||
}
|
||||
|
||||
DrawIndexedPrimitive();
|
||||
Draw(config);
|
||||
}
|
||||
|
||||
@@ -158,6 +158,9 @@ private:
|
||||
// Allocates a temporary CPU staging buffer, fires the callback with it to populate, then copies to a GPU buffer.
|
||||
bool AllocatePreinitializedGPUBuffer(u32 size, VkBuffer* gpu_buffer, VmaAllocation* gpu_allocation,
|
||||
VkBufferUsageFlags gpu_usage, const std::function<void(void*)>& fill_callback);
|
||||
|
||||
// Helper function for uploading indices.
|
||||
void UploadIndices(VKStreamBuffer& buffer, const void* index, size_t count);
|
||||
|
||||
union RenderPassCacheKey
|
||||
{
|
||||
@@ -383,6 +386,7 @@ private:
|
||||
|
||||
VKStreamBuffer m_vertex_stream_buffer;
|
||||
VKStreamBuffer m_index_stream_buffer;
|
||||
VKStreamBuffer m_expand_index_stream_buffer;
|
||||
VKStreamBuffer m_vertex_uniform_stream_buffer;
|
||||
VKStreamBuffer m_fragment_uniform_stream_buffer;
|
||||
VKStreamBuffer m_texture_stream_buffer;
|
||||
@@ -429,6 +433,7 @@ private:
|
||||
|
||||
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
|
||||
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
|
||||
GSHWDrawConfig::VSPushConstants m_vs_pc_cache;
|
||||
|
||||
std::string m_tfx_source;
|
||||
|
||||
@@ -528,9 +533,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;
|
||||
|
||||
@@ -562,6 +573,7 @@ public:
|
||||
|
||||
void IASetVertexBuffer(const void* vertex, size_t stride, 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, bool check_state);
|
||||
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
|
||||
@@ -571,6 +583,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;
|
||||
@@ -641,6 +654,7 @@ private:
|
||||
DIRTY_FLAG_PIPELINE = (1 << 12),
|
||||
DIRTY_FLAG_VS_CONSTANT_BUFFER = (1 << 13),
|
||||
DIRTY_FLAG_PS_CONSTANT_BUFFER = (1 << 14),
|
||||
DIRTY_FLAG_VS_PUSH_CONSTANTS = (1 << 15),
|
||||
|
||||
DIRTY_FLAG_TFX_TEXTURE_TEX = (DIRTY_FLAG_TFX_TEXTURE_0 << 0),
|
||||
DIRTY_FLAG_TFX_TEXTURE_PALETTE = (DIRTY_FLAG_TFX_TEXTURE_0 << 1),
|
||||
@@ -656,7 +670,7 @@ private:
|
||||
DIRTY_FLAG_BLEND_CONSTANTS | DIRTY_FLAG_LINE_WIDTH,
|
||||
DIRTY_TFX_STATE = DIRTY_BASE_STATE | DIRTY_FLAG_TFX_TEXTURES,
|
||||
DIRTY_UTILITY_STATE = DIRTY_BASE_STATE | DIRTY_FLAG_UTILITY_TEXTURE,
|
||||
DIRTY_CONSTANT_BUFFER_STATE = DIRTY_FLAG_VS_CONSTANT_BUFFER | DIRTY_FLAG_PS_CONSTANT_BUFFER,
|
||||
DIRTY_CONSTANT_BUFFER_STATE = DIRTY_FLAG_VS_CONSTANT_BUFFER | DIRTY_FLAG_PS_CONSTANT_BUFFER | DIRTY_FLAG_VS_PUSH_CONSTANTS,
|
||||
ALL_DIRTY_STATE = DIRTY_BASE_STATE | DIRTY_TFX_STATE | DIRTY_UTILITY_STATE | DIRTY_CONSTANT_BUFFER_STATE,
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -238,7 +238,7 @@ DEFINE_HOTKEY("ShutdownVM", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP(
|
||||
DEFINE_HOTKEY("ResetVM", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Reset Virtual Machine"),
|
||||
[](s32 pressed) {
|
||||
if (!pressed && VMManager::HasValidVM())
|
||||
VMManager::Reset();
|
||||
VMManager::RequestReset();
|
||||
})
|
||||
DEFINE_HOTKEY("ReloadPatches", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Reload Patches"),
|
||||
[](s32 pressed) {
|
||||
|
||||
@@ -649,7 +649,7 @@ void FullscreenUI::Render()
|
||||
if (s_about_window_open)
|
||||
DrawAboutWindow();
|
||||
|
||||
if (s_achievements_login_open)
|
||||
if (s_achievements_login_open || ImGui::IsPopupOpen("RetroAchievements"))
|
||||
DrawAchievementsLoginWindow();
|
||||
|
||||
if (s_input_binding_type != InputBindingInfo::Type::Unknown)
|
||||
@@ -3499,14 +3499,18 @@ bool FullscreenUI::OpenAchievementsWindow()
|
||||
|
||||
void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
{
|
||||
ImGui::SetNextWindowSize(LayoutScale(400.0f, 330.0f));
|
||||
if (s_achievements_login_open && !ImGui::IsPopupOpen("RetroAchievements"))
|
||||
ImGui::OpenPopup("RetroAchievements");
|
||||
|
||||
const float dialog_width = std::clamp(LayoutScale(420.0f), 300.0f, ImGui::GetIO().DisplaySize.x);
|
||||
ImGui::SetNextWindowSizeConstraints(ImVec2(dialog_width, 0.0f), ImVec2(dialog_width, ImGui::GetIO().DisplaySize.y));
|
||||
ImGui::SetNextWindowPos(ImGui::GetIO().DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, LayoutScale(ImGuiFullscreen::LAYOUT_WINDOW_ROUNDING));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, LayoutScale(24.0f, 24.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0.13f, 0.13f, 0.13f, 0.95f));
|
||||
|
||||
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar))
|
||||
if (ImGui::BeginPopupModal("RetroAchievements", &s_achievements_login_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysAutoResize))
|
||||
{
|
||||
const float content_width = ImGui::GetContentRegionAvail().x;
|
||||
|
||||
@@ -3553,7 +3557,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
|
||||
if (s_achievements_login_logging_in)
|
||||
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
|
||||
ImGui::BeginDisabled();
|
||||
|
||||
ImGui::SetNextItemWidth(content_width);
|
||||
@@ -3567,7 +3571,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
ImGui::PopStyleColor(5);
|
||||
ImGui::PopStyleVar(3);
|
||||
|
||||
if (s_achievements_login_logging_in)
|
||||
if (s_achievements_login_logging_in || s_achievements_login_show_dismiss)
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::Spacing();
|
||||
@@ -3582,20 +3586,67 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
else if (s_achievements_login_show_dismiss)
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.35f, 0.9f, 0.35f, 1.0f));
|
||||
const TinyString username = Host::GetBaseTinyStringSettingValue("Achievements", "Username", "");
|
||||
const SmallString success_text = SmallString::from_format(
|
||||
FSUI_FSTR("Successfully logged in as {}."), username.empty() ? "Unknown" : username.view());
|
||||
const float status_width = ImGui::CalcTextSize(success_text.c_str()).x;
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (content_width - status_width) * 0.5f);
|
||||
ImGui::TextUnformatted(success_text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::Spacing();
|
||||
}
|
||||
|
||||
const float button_height = LayoutScale(36.0f);
|
||||
const float button_width = LayoutScale(100.0f);
|
||||
const float button_height = std::max(LayoutScale(36.0f), 28.0f);
|
||||
const float button_spacing = LayoutScale(12.0f);
|
||||
const float total_width = (button_width * 2) + button_spacing;
|
||||
const float start_x = (content_width - total_width) * 0.5f;
|
||||
const float button_width = (content_width - button_spacing) * 0.5f;
|
||||
|
||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + start_x);
|
||||
auto CloseLoginPopup = []() {
|
||||
ImGui::CloseCurrentPopup();
|
||||
s_achievements_login_open = false;
|
||||
s_achievements_login_logging_in = false;
|
||||
s_achievements_login_show_dismiss = false;
|
||||
|
||||
s_achievements_login_username[0] = '\0';
|
||||
s_achievements_login_password[0] = '\0';
|
||||
};
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, LayoutScale(ImGuiFullscreen::LAYOUT_FRAME_ROUNDING));
|
||||
|
||||
const bool can_login = !s_achievements_login_logging_in &&
|
||||
strlen(s_achievements_login_username) > 0 &&
|
||||
strlen(s_achievements_login_password) > 0;
|
||||
const bool can_login = !s_achievements_login_show_dismiss && !s_achievements_login_logging_in &&
|
||||
strlen(s_achievements_login_username) > 0 &&
|
||||
strlen(s_achievements_login_password) > 0;
|
||||
|
||||
if (s_achievements_login_show_dismiss)
|
||||
{
|
||||
// keep dialog open and let user explicitly dismiss.
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.9f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.6f, 1.0f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.4f, 0.8f, 1.0f));
|
||||
|
||||
if (ImGui::Button(FSUI_CSTR("Dismiss"), ImVec2(button_width, button_height)) && !s_achievements_login_logging_in)
|
||||
CloseLoginPopup();
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine(0, button_spacing);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
ImGui::BeginDisabled();
|
||||
ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height));
|
||||
ImGui::EndDisabled();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (can_login)
|
||||
{
|
||||
@@ -3613,6 +3664,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
if (ImGui::Button(FSUI_CSTR("Login"), ImVec2(button_width, button_height)) && can_login)
|
||||
{
|
||||
s_achievements_login_logging_in = true;
|
||||
s_achievements_login_show_dismiss = false;
|
||||
|
||||
Host::RunOnCPUThread([username = std::string(s_achievements_login_username),
|
||||
password = std::string(s_achievements_login_password)]() {
|
||||
@@ -3624,14 +3676,10 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
if (!result)
|
||||
{
|
||||
ShowToast(std::string(), fmt::format(FSUI_FSTR("Login failed.\nError: {}\n\nPlease check your username and password, and try again."),
|
||||
error.GetDescription()));
|
||||
error.GetDescription()));
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
s_achievements_login_open = false;
|
||||
|
||||
s_achievements_login_username[0] = '\0';
|
||||
s_achievements_login_password[0] = '\0';
|
||||
|
||||
if (s_achievements_login_reason == Achievements::LoginRequestReason::UserInitiated)
|
||||
@@ -3683,11 +3731,12 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
s_achievements_login_show_dismiss = true;
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine(0, button_spacing);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
@@ -3702,12 +3751,7 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
Achievements::DisableHardcoreMode();
|
||||
}
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
s_achievements_login_open = false;
|
||||
s_achievements_login_logging_in = false;
|
||||
|
||||
s_achievements_login_username[0] = '\0';
|
||||
s_achievements_login_password[0] = '\0';
|
||||
CloseLoginPopup();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(3);
|
||||
@@ -3718,9 +3762,6 @@ void FullscreenUI::DrawAchievementsLoginWindow()
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
if (!ImGui::IsPopupOpen("RetroAchievements"))
|
||||
ImGui::OpenPopup("RetroAchievements");
|
||||
}
|
||||
|
||||
bool FullscreenUI::IsAchievementsWindowOpen()
|
||||
@@ -3960,11 +4001,14 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
|
||||
"sounds/achievements/lbsubmit.wav", FSUI_ICONSTR(ICON_FA_FOLDER_OPEN, "Select Leaderboard Submit Sound"));
|
||||
|
||||
MenuHeading(FSUI_CSTR("Account"));
|
||||
if (bsi->ContainsValue("Achievements", "Token"))
|
||||
SettingsInterface* secrets_si = Host::Internal::GetSecretsSettingsLayer();
|
||||
const TinyString username = bsi->GetTinyStringValue("Achievements", "Username", "");
|
||||
const bool has_token = (secrets_si && secrets_si->ContainsValue("Achievements", "Token"));
|
||||
if (has_token)
|
||||
{
|
||||
ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImGui::GetStyle().Colors[ImGuiCol_Text]);
|
||||
ActiveButton(SmallString::from_format(
|
||||
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), bsi->GetTinyStringValue("Achievements", "Username")),
|
||||
fmt::runtime(FSUI_ICONSTR(ICON_FA_USER, "Username: {}")), username.empty() ? "Unknown" : username.view()),
|
||||
false, false, ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
ActiveButton(SmallString::from_format(fmt::runtime(FSUI_ICONSTR(ICON_FA_CLOCK, "Login token generated on {}")),
|
||||
TimeToPrintableString(static_cast<time_t>(
|
||||
@@ -3984,6 +4028,7 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
|
||||
if (MenuButton(FSUI_ICONSTR(ICON_FA_KEY, "Login"), FSUI_CSTR("Logs in to RetroAchievements.")))
|
||||
{
|
||||
s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
|
||||
s_achievements_login_show_dismiss = false;
|
||||
s_achievements_login_open = true;
|
||||
}
|
||||
}
|
||||
@@ -4117,6 +4162,9 @@ TRANSLATE_NOOP("FullscreenUI", "Reset System");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This game has no achievements.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This game has no leaderboards.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "No file selected.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sound reset to default.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sound is already using default.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to Save State");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game List");
|
||||
@@ -4164,12 +4212,12 @@ TRANSLATE_NOOP("FullscreenUI", "Forces a full rescan of all games previously ide
|
||||
TRANSLATE_NOOP("FullscreenUI", "About PCSX2");
|
||||
TRANSLATE_NOOP("FullscreenUI", "PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Version: %s");
|
||||
TRANSLATE_NOOP("FullscreenUI", "RetroAchievements");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Please enter your user name and password for retroachievements.org below.\n\nYour password will not be saved in PCSX2, an access token will be generated and used instead.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Username");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Password");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Logging in...");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Dismiss");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Login");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Cancel");
|
||||
TRANSLATE_NOOP("FullscreenUI", "When enabled and logged in, PCSX2 will scan for achievements on startup.");
|
||||
@@ -4207,7 +4255,10 @@ TRANSLATE_NOOP("FullscreenUI", "Time Played: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Last Played: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Size: {:.2f} MB");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to reset the play time for '{}' ({})?\n\nYour current play time is {}.\n\nThis action cannot be undone.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Version: {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Successfully logged in as {}.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Login failed.\nError: {}\n\nPlease check your username and password, and try again.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to preview sound:\n{}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State From Backup Slot {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State From Slot {}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Failed to Save State To Slot {}");
|
||||
@@ -4289,6 +4340,9 @@ TRANSLATE_NOOP("FullscreenUI", "Notification Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Encore Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Spectator Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Test Unofficial Achievements");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Select File");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Preview");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Reset to Default");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Notification Sound");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Select Notification Sound");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Unlock Sound");
|
||||
|
||||
@@ -234,6 +234,7 @@ namespace FullscreenUI
|
||||
// achievements login dialog state
|
||||
inline bool s_achievements_login_open = false;
|
||||
inline bool s_achievements_login_logging_in = false;
|
||||
inline bool s_achievements_login_show_dismiss = false;
|
||||
inline char s_achievements_login_username[256] = {};
|
||||
inline char s_achievements_login_password[256] = {};
|
||||
inline Achievements::LoginRequestReason s_achievements_login_reason = Achievements::LoginRequestReason::UserInitiated;
|
||||
|
||||
@@ -2308,12 +2308,12 @@ void FullscreenUI::DrawInterfaceSettingsPage()
|
||||
}
|
||||
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Controller Glyph Style"),
|
||||
FSUI_CSTR("Changes which gamepad button glyph set is used in Big Picture UI and input binding displays."),
|
||||
(glyph_index < std::size(glyph_values)) ? FSUI_CSTR(glyph_names[glyph_index]) : FSUI_CSTR("Unknown")))
|
||||
(glyph_index < std::size(glyph_values)) ? Host::TranslateToCString(TR_CONTEXT, glyph_names[glyph_index]) : FSUI_CSTR("Unknown")))
|
||||
{
|
||||
ImGuiFullscreen::ChoiceDialogOptions cd_options;
|
||||
cd_options.reserve(std::size(glyph_values));
|
||||
for (size_t i = 0; i < std::size(glyph_values); i++)
|
||||
cd_options.emplace_back(FSUI_STR(glyph_names[i]), i == static_cast<size_t>(glyph_index));
|
||||
cd_options.emplace_back(Host::TranslateToString(TR_CONTEXT, glyph_names[i]), i == static_cast<size_t>(glyph_index));
|
||||
|
||||
OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Controller Glyph Style"), false, std::move(cd_options), [](s32 index, const std::string& title, bool checked) {
|
||||
if (index >= 0 && static_cast<size_t>(index) < std::size(glyph_values))
|
||||
@@ -2356,7 +2356,7 @@ void FullscreenUI::DrawInterfaceSettingsPage()
|
||||
swap_summery.format(FSUI_FSTR("Uses {} as confirm when using a controller."),
|
||||
glyphs.confirm(ImGui::GetIO().ConfigNavSwapGamepadButtons));
|
||||
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Swap OK/Cancel in Big Picture Mode"), swap_summery.c_str(),
|
||||
(swap_index < std::size(swap_values)) ? FSUI_CSTR(swap_names[swap_index]) : FSUI_CSTR("Unknown")))
|
||||
(swap_index < std::size(swap_values)) ? Host::TranslateToCString(TR_CONTEXT, swap_names[swap_index]) : FSUI_CSTR("Unknown")))
|
||||
{
|
||||
ImGuiFullscreen::ChoiceDialogOptions cd_options;
|
||||
cd_options.reserve(std::size(swap_values));
|
||||
@@ -2390,7 +2390,7 @@ void FullscreenUI::DrawInterfaceSettingsPage()
|
||||
swap_summery.format(FSUI_FSTR("Swaps both {}/{} (When Swap OK/Cancel is set to automatic) and {}/{} buttons"),
|
||||
glyphs.south, glyphs.east, glyphs.west, glyphs.north);
|
||||
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Use Legacy Nintendo Layout in Big Picture Mode"), swap_summery.c_str(),
|
||||
(nintendo_index < std::size(swap_values)) ? FSUI_CSTR(swap_names[nintendo_index]) : FSUI_CSTR("Unknown")))
|
||||
(nintendo_index < std::size(swap_values)) ? Host::TranslateToCString(TR_CONTEXT, swap_names[nintendo_index]) : FSUI_CSTR("Unknown")))
|
||||
{
|
||||
ImGuiFullscreen::ChoiceDialogOptions cd_options;
|
||||
cd_options.reserve(std::size(swap_values));
|
||||
@@ -5206,6 +5206,7 @@ TRANSLATE_NOOP("FullscreenUI", "Display a modal dialog when a save state load/sa
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Changes which gamepad button glyph set is used in Big Picture UI and input binding displays.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Integration");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the game you are currently playing as part of your profile on Discord.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game Display");
|
||||
@@ -5272,57 +5273,30 @@ TRANSLATE_NOOP("FullscreenUI", "Force a primitive flush when a framebuffer is al
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables emulation of the GS's edge anti-aliasing (AA1).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Hardware Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disables automatic hardware fixes, allowing you to set fixes manually.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Sprite Render Size");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uses software renderer to draw texture decompression-like sprites.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Sprite Render Level");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines filter level for CPU sprite render.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Software CLUT Render");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uses software renderer to draw texture CLUT points/sprites.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GPU Target CLUT");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Try to detect when a game is drawing its own color palette and then renders it on the GPU with special handling.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Draw Start");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Object range to skip drawing.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Draw End");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Auto Flush (Hardware)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Framebuffer Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Convert 4-bit and 8-bit framebuffer on the CPU instead of the GPU.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Depth Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable the support of depth buffers in the texture cache.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Safe Features");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This option disables multiple safe features.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Render Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "This option disables game-specific render fixes.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Preload Frame Data");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uploads GS data when rendering a new frame to reproduce some effects accurately.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Partial Invalidation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Removes texture cache entries when there is any intersection, rather than only the intersected areas.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Inside RT");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Allows the texture cache to reuse as an input texture the inner portion of a previous framebuffer.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Read Targets When Closing");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Flushes all targets in the texture cache back to local memory when shutting down.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Estimate Texture Region");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Attempts to reduce the texture size when games do not set it themselves (e.g. Snowblind games).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GPU Palette Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "When enabled GPU converts colormap-textures, otherwise the CPU will. It is a trade-off between GPU and CPU.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Upscaling Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Half Pixel Offset");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts vertices relative to upscaling.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Native Scaling");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Attempt to do rescaling at native resolution.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Round Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts sprite coordinates.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Bilinear Dirty Upscale");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Offset X");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts target texture offsets.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Offset Y");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Align Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Fixes issues with upscaling (vertical lines) in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Merge Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Replaces multiple post-processing sprites with a larger single sprite.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Force Even Sprite Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Unscaled Palette Texture Draws");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Can fix some broken effects which rely on pixel perfect precision.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Replacement");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Loads replacement textures where available and user-provided.");
|
||||
@@ -5341,35 +5315,22 @@ TRANSLATE_NOOP("FullscreenUI", "Filters");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables brightness/contrast/gamma/saturation adjustment.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts brightness. 50 is normal.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts contrast. 50 is normal.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost Gamma");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts gamma. 50 is normal.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Adjusts saturation. 50 is normal.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Applies a shader which replicates the visual effects of different styles of television set.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Advanced");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Presenting Duplicate Frames");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing worse.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Mailbox Presentation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Displays additional, very high upscaling multipliers dependent on GPU and driver capability.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Hardware Download Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Changes synchronization behavior for GS downloads.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Allow Exclusive Fullscreen");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Overrides the driver's heuristics for enabling exclusive fullscreen, or direct flip/scanout.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Override Texture Barriers");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Forces texture barrier functionality to the specified value.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GS Dump Compression");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Sets the compression algorithm for GS dumps.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Framebuffer Fetch");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Prevents the usage of framebuffer fetch when supported by host GPU.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Shader Cache");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Prevents the loading and saving of shaders/pipelines to disk.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Vertex Shader Expand");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Falls back to the CPU for expanding sprites/lines.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Preloading");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Uploads full textures to the GPU on use, rather than only the utilized regions. Can improve performance in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "NTSC Frame Rate");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines what frame rate NTSC games run at.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "PAL Frame Rate");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines what frame rate PAL games run at.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "On-Screen Display");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitors are.");
|
||||
@@ -5377,7 +5338,6 @@ TRANSLATE_NOOP("FullscreenUI", "Determines the distance in pixels from the edges
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines where on-screen display messages are positioned.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Determines where performance statistics are positioned.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Select OSD Font");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Clear OSD Font Override");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use default bundled font");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Draws OSD text with heavier weight for improved readability.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shows the current PCSX2 version.");
|
||||
@@ -5487,7 +5447,6 @@ TRANSLATE_NOOP("FullscreenUI", "Sets the compression level for savestate.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Graphics");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enables API-level validation of graphics commands.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Settings");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Cheats");
|
||||
TRANSLATE_NOOP("FullscreenUI", "No cheats are available for this game.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Cheat Codes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "No patches are available for this game.");
|
||||
@@ -5497,41 +5456,23 @@ TRANSLATE_NOOP("FullscreenUI", "Activating game patches can cause unpredictable
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use patches at your own risk, the PCSX2 team will provide no support for users who have enabled game patches.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Game fixes should not be modified unless you are aware of what each option does and the implications of doing so.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "FPU Multiply Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "For Tales of Destiny.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For FMVs");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Needed for some games with complex FMV rendering.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip MPEG Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skips videos/FMVs in games to avoid game hanging/freezes.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Preload TLB Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "To avoid TLB miss on Goemon.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "EE Timing Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Instant DMA Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OPH Flag Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulate GIF FIFO");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Correct but slower. Known to affect the following games: FIFA Street 2.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "DMA Busy Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Delay VIF1 Stalls");
|
||||
TRANSLATE_NOOP("FullscreenUI", "For SOCOM 2 HUD and Spy Hunter loading hang.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulate VIF FIFO");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Full VU0 Synchronization");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Forces tight VU0 sync on every COP2 instruction.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU I Bit Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Avoids constant recompilation in some games. Known to affect the following games: Scarface The World is Yours, Crash Tag Team Racing.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Add Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Overflow Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "To check for possible float overflows (Superman Returns).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Sync");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Run behind. To avoid sync problems when reading or writing VU registers.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU XGKick Sync");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use accurate timing for VU XGKicks (slower).");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Force Blit Internal FPS Detection");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use alternative method to calculate internal FPS to avoid false readings in some games.");
|
||||
TRANSLATE_NOOP("FullscreenUI", "{0}/{1}/{2}/{3}");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Automatic mapping completed for {}.");
|
||||
@@ -5595,6 +5536,9 @@ TRANSLATE_NOOP("FullscreenUI", "Fill");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Stretch");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Center");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Tile");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Xbox");
|
||||
TRANSLATE_NOOP("FullscreenUI", "PlayStation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Nintendo");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enabled");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disabled");
|
||||
TRANSLATE_NOOP("FullscreenUI", "2% [1 FPS (NTSC) / 1 FPS (PAL)]");
|
||||
@@ -5818,6 +5762,7 @@ TRANSLATE_NOOP("FullscreenUI", "Prompt On State Load/Save Failure");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Confirm Shutdown");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Save State On Shutdown");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Create Save State Backups");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Controller Glyph Style");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Swap OK/Cancel in Big Picture Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Legacy Nintendo Layout in Big Picture Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence");
|
||||
@@ -5835,6 +5780,7 @@ TRANSLATE_NOOP("FullscreenUI", "EE Cycle Rate");
|
||||
TRANSLATE_NOOP("FullscreenUI", "EE Cycle Skipping");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable MTVU (Multi-Threaded VU1)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Thread Pinning");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Cheats");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Host Filesystem");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable Fast CDVD");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Enable CDVD Precaching");
|
||||
@@ -5869,6 +5815,33 @@ TRANSLATE_NOOP("FullscreenUI", "Software Rendering Threads");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Auto Flush (Software)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Edge AA (AA1)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Manual Hardware Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Sprite Render Size");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Sprite Render Level");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Software CLUT Render");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GPU Target CLUT");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Draw Start");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Draw End");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Auto Flush (Hardware)");
|
||||
TRANSLATE_NOOP("FullscreenUI", "CPU Framebuffer Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Depth Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Safe Features");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Render Fixes");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Preload Frame Data");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Partial Invalidation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Inside RT");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Read Targets When Closing");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Estimate Texture Region");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GPU Palette Conversion");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Half Pixel Offset");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Native Scaling");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Round Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Bilinear Dirty Upscale");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Offset X");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Offset Y");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Align Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Merge Sprite");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Force Even Sprite Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Unscaled Palette Texture Draws");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Load Textures");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Asynchronous Texture Loading");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Precache Replacements");
|
||||
@@ -5882,14 +5855,28 @@ TRANSLATE_NOOP("FullscreenUI", "CAS Sharpness");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost Brightness");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost Contrast");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost Gamma");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Shade Boost Saturation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "TV Shaders");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip Presenting Duplicate Frames");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Mailbox Presentation");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Extended Upscaling Multipliers");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Hardware Download Mode");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Allow Exclusive Fullscreen");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Override Texture Barriers");
|
||||
TRANSLATE_NOOP("FullscreenUI", "GS Dump Compression");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Framebuffer Fetch");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Shader Cache");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Disable Vertex Shader Expand");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Texture Preloading");
|
||||
TRANSLATE_NOOP("FullscreenUI", "NTSC Frame Rate");
|
||||
TRANSLATE_NOOP("FullscreenUI", "PAL Frame Rate");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OSD Scale");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OSD Margin");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OSD Messages Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OSD Performance Position");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OSD Font File");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Clear OSD Font Override");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Bold OSD Text");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show PCSX2 Version");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Show Speed");
|
||||
@@ -6010,6 +5997,24 @@ TRANSLATE_NOOP("FullscreenUI", "Enable IOP Recompiler");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Compression Method");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Compression Level");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Debug Device");
|
||||
TRANSLATE_NOOP("FullscreenUI", "FPU Multiply Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Use Software Renderer For FMVs");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Skip MPEG Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Preload TLB Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "EE Timing Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Instant DMA Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "OPH Flag Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulate GIF FIFO");
|
||||
TRANSLATE_NOOP("FullscreenUI", "DMA Busy Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Delay VIF1 Stalls");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Emulate VIF FIFO");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Full VU0 Synchronization");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU I Bit Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Add Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Overflow Hack");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU Sync");
|
||||
TRANSLATE_NOOP("FullscreenUI", "VU XGKick Sync");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Force Blit Internal FPS Detection");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Memory Card Enabled");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Card Name");
|
||||
TRANSLATE_NOOP("FullscreenUI", "Eject Card");
|
||||
|
||||
@@ -835,6 +835,9 @@ __ri void ImGuiManager::DrawSettingsOverlay(float scale, float margin, float spa
|
||||
|
||||
if (GSConfig.HWAccurateAlphaTest)
|
||||
APPEND("AAT ");
|
||||
|
||||
if (GSConfig.HWAA1)
|
||||
APPEND("AA1 ");
|
||||
|
||||
// deliberately test global and print local here for auto values
|
||||
if (EmuConfig.GS.TextureFiltering != BiFiltering::PS2)
|
||||
|
||||
@@ -709,7 +709,7 @@ __fi void psxRcntWmode32(int index, u32 value)
|
||||
|
||||
// Current counter *always* resets on mode write.
|
||||
counter.count = 0;
|
||||
counter.startCycle = psxRegs.cycle & ~(counter.rate - 1);
|
||||
counter.startCycle = psxRegs.cycle & ~(static_cast<u64>(counter.rate - 1));
|
||||
counter.target &= 0xffffffff;
|
||||
_rcntSet(index);
|
||||
}
|
||||
|
||||
+46
-36
@@ -753,6 +753,7 @@ Pcsx2Config::GSOptions::GSOptions()
|
||||
Mipmap = true;
|
||||
HWMipmap = true;
|
||||
HWAccurateAlphaTest = false;
|
||||
HWAA1 = false;
|
||||
|
||||
ManualUserHacks = false;
|
||||
UserHacks_AlignSpriteX = false;
|
||||
@@ -908,6 +909,7 @@ bool Pcsx2Config::GSOptions::RestartOptionsAreEqual(const GSOptions& right) cons
|
||||
OpEqu(DisableVertexShaderExpand) &&
|
||||
OpEqu(OverrideTextureBarriers) &&
|
||||
OpEqu(DepthFeedbackMode) &&
|
||||
OpEqu(HWAA1) &&
|
||||
OpEqu(ExclusiveFullscreenControl);
|
||||
}
|
||||
|
||||
@@ -1038,6 +1040,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
|
||||
|
||||
SettingsWrapBitBoolEx(HWMipmap, "hw_mipmap");
|
||||
SettingsWrapBitBool(HWAccurateAlphaTest);
|
||||
SettingsWrapBitBool(HWAA1);
|
||||
SettingsWrapIntEnumEx(AccurateBlendingUnit, "accurate_blending_unit");
|
||||
SettingsWrapIntEnumEx(TextureFiltering, "filter");
|
||||
SettingsWrapIntEnumEx(TexturePreloading, "texture_preloading");
|
||||
@@ -2192,51 +2195,58 @@ std::string EmuFolders::GetPortableModePath()
|
||||
|
||||
bool EmuFolders::SetDataDirectory(Error* error)
|
||||
{
|
||||
// Portable mode has the absolute priority.
|
||||
if (!ShouldUsePortableMode())
|
||||
{
|
||||
// Also check if the user has overriden the DataRoot path.
|
||||
if (EmuConfig.CustomDataPath.empty())
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
// On Windows, use My Documents\PCSX2 to match old installs.
|
||||
PWSTR documents_directory;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory)))
|
||||
{
|
||||
if (std::wcslen(documents_directory) > 0)
|
||||
DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2");
|
||||
CoTaskMemFree(documents_directory);
|
||||
}
|
||||
// On Windows, use My Documents\PCSX2 to match old installs.
|
||||
PWSTR documents_directory;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &documents_directory)))
|
||||
{
|
||||
if (std::wcslen(documents_directory) > 0)
|
||||
DataRoot = Path::Combine(StringUtil::WideStringToUTF8String(documents_directory), "PCSX2");
|
||||
CoTaskMemFree(documents_directory);
|
||||
}
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
// Use $XDG_CONFIG_HOME/PCSX2 if it exists.
|
||||
const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
|
||||
if (xdg_config_home && Path::IsAbsolute(xdg_config_home))
|
||||
{
|
||||
DataRoot = Path::RealPath(Path::Combine(xdg_config_home, "PCSX2"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use ~/PCSX2 for non-XDG, and ~/.config/PCSX2 for XDG.
|
||||
// Use $XDG_CONFIG_HOME/PCSX2 if it exists.
|
||||
const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
|
||||
if (xdg_config_home && Path::IsAbsolute(xdg_config_home))
|
||||
{
|
||||
DataRoot = Path::RealPath(Path::Combine(xdg_config_home, "PCSX2"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use ~/PCSX2 for non-XDG, and ~/.config/PCSX2 for XDG.
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (home_dir)
|
||||
{
|
||||
// ~/.config should exist, but just in case it doesn't and this is a fresh profile..
|
||||
const std::string config_dir(Path::Combine(home_dir, ".config"));
|
||||
if (!FileSystem::DirectoryExists(config_dir.c_str()))
|
||||
FileSystem::CreateDirectoryPath(config_dir.c_str(), false);
|
||||
|
||||
DataRoot = Path::RealPath(Path::Combine(config_dir, "PCSX2"));
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
static constexpr char MAC_DATA_DIR[] = "Library/Application Support/PCSX2";
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (home_dir)
|
||||
{
|
||||
// ~/.config should exist, but just in case it doesn't and this is a fresh profile..
|
||||
const std::string config_dir(Path::Combine(home_dir, ".config"));
|
||||
if (!FileSystem::DirectoryExists(config_dir.c_str()))
|
||||
FileSystem::CreateDirectoryPath(config_dir.c_str(), false);
|
||||
|
||||
DataRoot = Path::RealPath(Path::Combine(config_dir, "PCSX2"));
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
static constexpr char MAC_DATA_DIR[] = "Library/Application Support/PCSX2";
|
||||
const char* home_dir = getenv("HOME");
|
||||
if (home_dir)
|
||||
DataRoot = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR));
|
||||
DataRoot = Path::RealPath(Path::Combine(home_dir, MAC_DATA_DIR));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else // Otherwise use the custom path provided by the user
|
||||
DataRoot = Path::RealPath(Path::Combine(EmuConfig.CustomDataPath, "PCSX2"));
|
||||
}
|
||||
|
||||
// couldn't determine the data directory, or using portable mode? fallback to portable.
|
||||
// Couldn't determine the data directory, or using portable mode? fallback to portable.
|
||||
if (DataRoot.empty())
|
||||
{
|
||||
#if defined(__linux__)
|
||||
// special check if we're on appimage
|
||||
// Special check if we're on appimage
|
||||
// always make sure that DataRoot
|
||||
// is adjacent next to the appimage
|
||||
if (getenv("APPIMAGE"))
|
||||
@@ -2251,10 +2261,10 @@ bool EmuFolders::SetDataDirectory(Error* error)
|
||||
#endif
|
||||
}
|
||||
|
||||
// inis is always below the data root
|
||||
// Inis is always below the data root
|
||||
Settings = Path::Combine(DataRoot, "inis");
|
||||
|
||||
// make sure it exists
|
||||
// Make sure it exists
|
||||
Console.WriteLnFmt("DataRoot Directory: {}", DataRoot);
|
||||
return (FileSystem::EnsureDirectoryExists(DataRoot.c_str(), false, error) &&
|
||||
FileSystem::EnsureDirectoryExists(Settings.c_str(), false, error));
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
/// Version number for GS and other shaders. Increment whenever any of the contents of the
|
||||
/// shaders change, to invalidate the cache.
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 85; // Last changed in PR 14195
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 92; // Last changed in PR 14399
|
||||
|
||||
@@ -29,6 +29,20 @@ namespace usb_msd
|
||||
"00000000000PCSX2",
|
||||
};
|
||||
|
||||
static const USBDescStrings olympus_camera_desc_strings = {
|
||||
"",
|
||||
"OLYMPUS",
|
||||
"USB Mass Storage",
|
||||
"00000000000PCSX2",
|
||||
};
|
||||
|
||||
static const USBDescStrings olympus_mo_desc_strings = {
|
||||
"",
|
||||
"OLYMPUS",
|
||||
"MOS3412",
|
||||
"00000000000PCSX2",
|
||||
};
|
||||
|
||||
static const USBDescStrings sony_msac_desc_strings = {
|
||||
"",
|
||||
"Sony",
|
||||
@@ -38,14 +52,14 @@ namespace usb_msd
|
||||
static const uint8_t zip100_dev_descriptor[] = {
|
||||
0x12, // bLength
|
||||
0x01, // bDescriptorType (Device)
|
||||
0x10, 0x01, // bcdUSB 0.10
|
||||
0x10, 0x01, // bcdUSB 1.10
|
||||
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
|
||||
0x00, // bDeviceSubClass
|
||||
0x00, // bDeviceProtocol
|
||||
0x40, // bMaxPacketSize0 8
|
||||
0x9B, 0x05, // idVendor 0x059B
|
||||
0x34, 0x00, // idProduct 0x0034
|
||||
0x00, 0x01, // bcdDevice 0.00
|
||||
0x00, 0x01, // bcdDevice 1.00
|
||||
0x01, // iManufacturer (String Index)
|
||||
0x02, // iProduct (String Index)
|
||||
0x03, // iSerialNumber (String Index)
|
||||
@@ -94,6 +108,23 @@ namespace usb_msd
|
||||
0x20, // bInterval 32 (unit depends on device speed)
|
||||
};
|
||||
|
||||
static const uint8_t olympus_dev_descriptor[] = {
|
||||
0x12, // bLength
|
||||
0x01, // bDescriptorType (Device)
|
||||
0x10, 0x01, // bcdUSB 1.10
|
||||
0x00, // bDeviceClass (Use class information in the Interface Descriptors)
|
||||
0x00, // bDeviceSubClass
|
||||
0x00, // bDeviceProtocol
|
||||
0x40, // bMaxPacketSize0 8
|
||||
0xb4, 0x07, // idVendor 0x07b4
|
||||
0x02, 0x01, // idProduct 0x0102
|
||||
0x00, 0x01, // bcdDevice 1.00
|
||||
0x01, // iManufacturer (String Index)
|
||||
0x02, // iProduct (String Index)
|
||||
0x03, // iSerialNumber (String Index)
|
||||
0x01, // bNumConfigurations 1
|
||||
};
|
||||
|
||||
static const uint8_t sony_msac_dev_descriptor[] = {
|
||||
0x12, // bLength
|
||||
0x01, // bDescriptorType (Device)
|
||||
@@ -535,7 +566,7 @@ namespace usb_msd
|
||||
len = std::min<size_t>(len, s->f.data_len);
|
||||
|
||||
//TODO No async reader/writer so do it right here
|
||||
if (s->f.tag == s->f.file_op_tag)
|
||||
if (s->f.file_op_tag)
|
||||
{
|
||||
switch (s->f.mode)
|
||||
{
|
||||
@@ -586,6 +617,7 @@ namespace usb_msd
|
||||
|
||||
s->f.result = COMMAND_PASSED;
|
||||
s->f.off = 0;
|
||||
s->f.file_op_tag = false;
|
||||
if (cbw->cmd[0] != REQUEST_SENSE)
|
||||
set_sense(s, SENSE_CODE(NO_SENSE));
|
||||
|
||||
@@ -698,7 +730,7 @@ namespace usb_msd
|
||||
xfer_len = bswap32(*(uint32_t*)&cbw->cmd[6]);
|
||||
|
||||
s->f.data_len = xfer_len * LBA_BLOCK_SIZE;
|
||||
s->f.file_op_tag = s->f.tag;
|
||||
s->f.file_op_tag = true;
|
||||
|
||||
|
||||
if (xfer_len == 0) // nothing to do
|
||||
@@ -733,7 +765,7 @@ namespace usb_msd
|
||||
xfer_len = bswap32(*(uint32_t*)&cbw->cmd[6]);
|
||||
|
||||
s->f.data_len = xfer_len * LBA_BLOCK_SIZE;
|
||||
s->f.file_op_tag = s->f.tag;
|
||||
s->f.file_op_tag = true;
|
||||
|
||||
if (xfer_len == 0) //nothing to do
|
||||
break;
|
||||
@@ -1125,6 +1157,26 @@ namespace usb_msd
|
||||
s->dev.klass.handle_control = usb_msd_handle_control;
|
||||
s->dev.klass.handle_data = usb_msd_handle_data;
|
||||
break;
|
||||
case OLYMPUS_CAMERA:
|
||||
path = USB::GetConfigString(si, port, TypeName(), "ImagePathOlympusCamera");
|
||||
s->desc.str = olympus_camera_desc_strings;
|
||||
if (usb_desc_parse_dev(olympus_dev_descriptor, sizeof(olympus_dev_descriptor), s->desc, s->desc_dev) < 0)
|
||||
goto fail;
|
||||
if (usb_desc_parse_config(zip100_config_descriptor, sizeof(zip100_config_descriptor), s->desc_dev) < 0)
|
||||
goto fail;
|
||||
s->dev.klass.handle_control = usb_msd_handle_control;
|
||||
s->dev.klass.handle_data = usb_msd_handle_data;
|
||||
break;
|
||||
case OLYMPUS_MO:
|
||||
path = USB::GetConfigString(si, port, TypeName(), "ImagePathOlympusMD");
|
||||
s->desc.str = olympus_mo_desc_strings;
|
||||
if (usb_desc_parse_dev(olympus_dev_descriptor, sizeof(olympus_dev_descriptor), s->desc, s->desc_dev) < 0)
|
||||
goto fail;
|
||||
if (usb_desc_parse_config(zip100_config_descriptor, sizeof(zip100_config_descriptor), s->desc_dev) < 0)
|
||||
goto fail;
|
||||
s->dev.klass.handle_control = usb_msd_handle_control;
|
||||
s->dev.klass.handle_data = usb_msd_handle_data;
|
||||
break;
|
||||
case SONY_MSAC_US1:
|
||||
path = USB::GetConfigString(si, port, TypeName(), "ImagePathMsac");
|
||||
s->desc.str = sony_msac_desc_strings;
|
||||
@@ -1230,6 +1282,8 @@ namespace usb_msd
|
||||
{
|
||||
static const char* subtypes[] = {
|
||||
TRANSLATE_NOOP("USB", "Iomega Zip-100 (Generic)"),
|
||||
TRANSLATE_NOOP("USB", "Olympus Camera"),
|
||||
TRANSLATE_NOOP("USB", "Olympus Turbo MO"),
|
||||
TRANSLATE_NOOP("USB", "Sony MSAC-US1 (PictureParadise)")
|
||||
};
|
||||
return subtypes;
|
||||
@@ -1241,18 +1295,42 @@ namespace usb_msd
|
||||
{
|
||||
case IOMEGA_ZIP_100:
|
||||
{
|
||||
static constexpr const SettingInfo settings[] = {
|
||||
{SettingInfo::Type::Path, "ImagePathMsd", TRANSLATE_NOOP("USB", "Image Path"),
|
||||
TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")},
|
||||
};
|
||||
static constexpr const SettingInfo settings[] = {{
|
||||
.type = SettingInfo::Type::Path,
|
||||
.name = "ImagePathMsd",
|
||||
.display_name = TRANSLATE_NOOP("USB", "Image Path"),
|
||||
.description = TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")
|
||||
}};
|
||||
return settings;
|
||||
}
|
||||
case OLYMPUS_CAMERA:
|
||||
{
|
||||
static constexpr const SettingInfo settings[] = {{
|
||||
.type = SettingInfo::Type::Path,
|
||||
.name = "ImagePathOlympusCamera",
|
||||
.display_name = TRANSLATE_NOOP("USB", "Image Path"),
|
||||
.description = TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")
|
||||
}};
|
||||
return settings;
|
||||
}
|
||||
case OLYMPUS_MO:
|
||||
{
|
||||
static constexpr const SettingInfo settings[] = {{
|
||||
.type = SettingInfo::Type::Path,
|
||||
.name = "ImagePathOlympusMD",
|
||||
.display_name = TRANSLATE_NOOP("USB", "Image Path"),
|
||||
.description = TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")
|
||||
}};
|
||||
return settings;
|
||||
}
|
||||
case SONY_MSAC_US1:
|
||||
{
|
||||
static constexpr const SettingInfo settings[] = {
|
||||
{SettingInfo::Type::Path, "ImagePathMsac", TRANSLATE_NOOP("USB", "Image Path"),
|
||||
TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")},
|
||||
};
|
||||
static constexpr const SettingInfo settings[] = {{
|
||||
.type = SettingInfo::Type::Path,
|
||||
.name = "ImagePathMsac",
|
||||
.display_name = TRANSLATE_NOOP("USB", "Image Path"),
|
||||
.description = TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")
|
||||
}};
|
||||
return settings;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace usb_msd
|
||||
enum MSDType
|
||||
{
|
||||
IOMEGA_ZIP_100,
|
||||
OLYMPUS_CAMERA,
|
||||
OLYMPUS_MO,
|
||||
SONY_MSAC_US1,
|
||||
};
|
||||
|
||||
|
||||
@@ -1743,6 +1743,22 @@ void VMManager::Shutdown(bool save_resume_state)
|
||||
LoadSettings();
|
||||
}
|
||||
|
||||
bool VMManager::RequestReset()
|
||||
{
|
||||
if (MemcardBusy::IsBusy())
|
||||
{
|
||||
Host::AddIconOSDMessage("RequestReset", ICON_FA_TRIANGLE_EXCLAMATION,
|
||||
TRANSLATE_STR("VMManager",
|
||||
"The memory card is busy, so the reset operation has been cancelled to prevent data loss."),
|
||||
Host::OSD_WARNING_DURATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
VMManager::Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VMManager::Reset()
|
||||
{
|
||||
pxAssert(HasValidVM());
|
||||
@@ -3276,6 +3292,11 @@ void VMManager::WarnAboutUnsafeSettings()
|
||||
append(ICON_FA_IMAGES,
|
||||
TRANSLATE_SV("VMManager", "Accurate Alpha Test is enabled, this may reduce performance."));
|
||||
}
|
||||
if (EmuConfig.GS.HWAA1)
|
||||
{
|
||||
append(ICON_FA_CIRCLE_EXCLAMATION,
|
||||
TRANSLATE_SV("VMManager", "AA1 is enabled, this may severely degrade performance."));
|
||||
}
|
||||
if (EmuConfig.GS.DepthFeedbackMode != GSDepthFeedbackMode::Auto)
|
||||
{
|
||||
append(ICON_FA_IMAGES,
|
||||
|
||||
@@ -124,6 +124,9 @@ namespace VMManager
|
||||
/// Destroys all system components.
|
||||
void Shutdown(bool save_resume_state);
|
||||
|
||||
/// Resets all subsystems to a cold boot if it's safe to do so.
|
||||
bool RequestReset();
|
||||
|
||||
/// Resets all subsystems to a cold boot.
|
||||
void Reset();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user