Compare commits

..

35 Commits

Author SHA1 Message Date
Mrlinkwii
b4293a40d2 Docs: update Gamedb documentation 2026-01-30 18:06:59 +01:00
Mrlinkwii
9acadb21fe GameDB: remove left over schemea options 2026-01-30 18:06:02 +01:00
TJnotJT
e82fa0bba5 GS/HW: Require 32 bit RT for accumulation blend on max blend. 2026-01-30 11:06:13 +01:00
Ziemas
45490d903a SPU: Slow down DMA 2026-01-29 22:26:28 -05:00
PCSX2 Bot
76dadf792a [ci skip] Qt: Update Base Translation. 2026-01-30 02:03:26 +01:00
wxvu
204829865d GameDB: Add fixes for Puchi Copter 2 2026-01-29 14:21:14 +01:00
lightningterror
84dc2959c5 GSDumpRunner: Use utf-8 encoding for opening files.
Allows the dump compare to work normally with unique characters.
2026-01-29 14:17:02 +01:00
refractionpcsx2
a85b203689 GS/TC: More fixes for dst_matches and old target deletion. 2026-01-29 14:17:02 +01:00
lightningterror
135d40fb7f GS/TC: Remove targets in reverse lookup if the targets are old. 2026-01-29 14:17:02 +01:00
lightningterror
a0bc7a5d0e GS/TC: Update depth lookup when looking up targets. 2026-01-29 14:17:02 +01:00
SternXD
955b925633 FullscreenUI: Run translation script
Signed-off-by: SternXD <stern@sidestore.io>
2026-01-29 14:15:17 +01:00
SternXD
cc338cdd9d Tools: Refactor translation string extraction to support multiple source files
Signed-off-by: SternXD <stern@sidestore.io>

f
2026-01-29 14:15:17 +01:00
SternXD
082a28dc13 FullscreenUI: Cleanup headers
Signed-off-by: SternXD <stern@sidestore.io>
2026-01-29 14:15:17 +01:00
SternXD
664e14bd6c FullscreenUI: Extract Settings into separate source and add internal header
Signed-off-by: SternXD <stern@sidestore.io>
2026-01-29 14:15:17 +01:00
refractionpcsx2
7ea33400a9 GS: Update the stored transfer rect if worked out to be different 2026-01-29 14:13:39 +01:00
refractionpcsx2
32a3e8e62d GS/HW: On EE->GS transfer only invalidate area actually transferred 2026-01-29 14:13:39 +01:00
refractionpcsx2
fa953d7bb3 GS/TC: Allow creation of target during GS->GS transfer with offset 2026-01-29 14:12:45 +01:00
TheLastRar
66cd51bcd5 GS/DX: Fix per game fullscreen mode setting 2026-01-29 14:11:58 +01:00
PCSX2 Bot
ed7ebb77ca [ci skip] Qt: Update Base Translation. 2026-01-29 02:44:25 +01:00
Ziemas
10fc9a790d SPU: Emulate voice decode buffers
This makes the timing of NAX advancing more similar to console since it
emulates the decode buffer behaviour of it rushing ahead of playback
until the buffer is full.

It also makes interpolation of the first four samples more correct by
using real data instead of the zero filled previous values.

[SAVEVERSION+]
2026-01-28 12:18:43 -05:00
Ziemas
c42330eebf SPU: Remove unused voice struct members
Might as well If the saveversion is already being bumped.

[SAVEVERSION+]
2026-01-28 12:18:43 -05:00
Ziemas
680e05fead SPU: clang-format mixer.cpp 2026-01-28 12:18:43 -05:00
chaoticgd
1f7b98bf6b Debugger: Allow removing conditions from the breakpoint dialog 2026-01-28 12:13:39 -05:00
TheLastRar
c3a20d421e GS/DX12: Fix recreating swapchain failing on vsync mode changes 2026-01-28 01:30:12 +01:00
TheLastRar
f03ab6f728 Deps: Bump ffnvcodec to 13.0.19.0 2026-01-28 01:14:08 +01:00
PCSX2 Bot
e9275d78b5 [ci skip] Qt: Update Base Translation. 2026-01-28 01:04:00 +01:00
lightningterror
8ababb3890 GS/DX/VK: Properly align uniform buffers.
Make sure uniform buffers are 16 bytes aligned.
Make sure full size is 32 bytes with padding, was 28 previously which can be bad for cpu cache.
2026-01-28 01:00:36 +01:00
Mrlinkwii
422aba4b20 UI : rename fast boot option heading 2026-01-27 12:01:20 +01:00
wxvu
045b9bbf40 GameDB: Add EE Nearest Rounding to Steambot Chronicles (Bumpy Trot)
GameDB: Add EE Nearest Rounding to Steambot Chronicles (Bumpy Trot)

GameDB: Add EE Nearest Rounding to Steambot Chronicles (Bumpy Trot)
2026-01-27 12:00:25 +01:00
PCSX2 Bot
1f519acf92 [ci skip] Qt: Update Base Translation. 2026-01-27 11:59:55 +01:00
lightningterror
ac9ebdecba GS/DX12: Check if D3D12GetInterface is supported first.
On older versions of Windows 10 (example 2019 LTSC) D3D12GetInterface may fail because it doesn't exist, in such case we can check if D3D12GetInterface exists first.
2026-01-27 00:09:43 +01:00
Ariel Nogueira Kovaljski
1861394216 Qt: Fix shortcut creation when special folders have been moved.
Use SHGetKnownFolderPath to get the path of special folders instead of building the path from %USERPROFILE%.

Special folders like "Desktop" and "Start Menu\Programs" can be moved from their default paths, which breaks the shortcut creation due to the assumption that they will always be present in the user's home directory (%USERPROFILE%).
2026-01-26 23:59:24 +01:00
PCSX2 Bot
11cc884c96 [ci skip] PAD: Update to latest controller database. 2026-01-26 23:34:42 +01:00
TJnotJT
5710c2740c GS/HW: Enable Z floor only when needed. 2026-01-26 23:30:09 +01:00
TJnotJT
ec96feb22e GS/HW: Use conservative depth for shader depth output. 2026-01-26 23:30:09 +01:00
43 changed files with 8798 additions and 9126 deletions

View File

@@ -25,7 +25,7 @@ LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.2
LIBPNG=1.6.53
LIBWEBP=1.6.0
NVENC=11.1.5.3
NVENC=13.0.19.0
SDL=SDL3-3.4.0
QT=6.10.1
QTAPNG=1.3.0
@@ -57,7 +57,7 @@ e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWE
082cbf5f429e0d80820f68dc2b507a94d4cc1b4e70817b119bbb8ec6a69584b8 $SDL.tar.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
2974b91062197e0527dffa3aadd8fe3bfa6681ae45f5ff9181bc0ca6479abd59 nv-codec-headers-$NVENC.tar.gz
13da39edb3a40ed9713ae390ca89faa2f1202c9dda869ef306a8d4383e242bee nv-codec-headers-$NVENC.tar.gz
c465aa56757e7746ac707f582b6e2d51546569a4a2488c1172fb543aa5fdfc2c vulkan-sdk-$VULKAN.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
5a6226f7e23db51fdc3223121eba53f3f5447cf0cc4d6cb82a3a2df7a65d265d qtbase-everywhere-src-$QT.tar.xz

Binary file not shown.

View File

@@ -2075,6 +2075,8 @@ SCAJ-20128:
SCAJ-20129:
name: "Ponkotsu Roman Daikatsugeki Bumpy Trot"
region: "NTSC-Unk"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -7614,6 +7616,8 @@ SCKA-20058:
name: "액션 로망 범피 트롯"
name-en: "Action Romance Bumpy Trot"
region: "NTSC-K"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -24777,6 +24781,11 @@ SLES-53820:
SLES-53821:
name: "Radio Helicopter II"
region: "PAL-E"
patches:
9A695202:
content: |-
comment=Patch that nops a branch instruction causing a freeze.
patch=1,EE,001799AC,word,00000000
SLES-53824:
name: "Trapt"
region: "PAL-E"
@@ -25578,6 +25587,8 @@ SLES-54137:
SLES-54138:
name: "Steambot Chronicles"
region: "PAL-E"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -26228,6 +26239,8 @@ SLES-54333:
SLES-54335:
name: "Steambot Chronicles"
region: "PAL-F"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -35853,6 +35866,8 @@ SLPM-60255:
name-sort: "ぽんこつろまんだいかつげきばんぴーとろっと [たいけんばん]"
name-en: "Ponkotsu Roeman Daikatsugeki Bumpy Trot [Trial]"
region: "NTSC-J"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -35924,6 +35939,8 @@ SLPM-60266:
name-sort: "ぽんこつろまんだいかつげきばんぴーとろっと [たいけんばん]"
name-en: "Ponkotsu Roeman Daikatsugeki Bumpy Trot [Trial]"
region: "NTSC-J"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -40077,6 +40094,11 @@ SLPM-62624:
name-sort: "ぷちこぷたー2"
name-en: "Petit Copter 2"
region: "NTSC-J"
patches:
9A695202:
content: |-
comment=Patch that nops a branch instruction causing a freeze.
patch=1,EE,001799B8,word,00000000
SLPM-62625:
name: "鬼浜爆走愚連隊 激闘編"
name-sort: "おにはまばくそうぐれんたい げきとうへん"
@@ -59824,6 +59846,8 @@ SLPS-25457:
name-sort: "ぽんこつろまんだいかつげきばんぴーとろっと"
name-en: "Ponkotsu Roeman Daikatsugeki Bumpy Trot"
region: "NTSC-J"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -61228,6 +61252,8 @@ SLPS-25683:
name-sort: "ぽんこつろまんだいかつげきばんぴーとろっと [Irem COLLECTION]"
name-en: "Ponkotsu Roman Daikatsugeki Bumpy Trot [Irem Collection]"
region: "NTSC-J"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -71360,6 +71386,8 @@ SLUS-21344:
name: "Steambot Chronicles"
region: "NTSC-U"
compat: 5
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -74901,6 +74929,8 @@ SLUS-28059:
SLUS-28061:
name: "Steambot Chronicles [Trade Demo]"
region: "NTSC-U"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.
@@ -75679,6 +75709,8 @@ SLUS-29185:
SLUS-29188:
name: "Steambot Chronicles [Regular Demo]"
region: "NTSC-U"
roundModes:
eeRoundMode: 0 # Fixes broken load triggers.
gsHWFixes:
getSkipCount: "GSC_IRem"
halfPixelOffset: 2 # Aligns effects.

View File

@@ -236,6 +236,7 @@
03000000ac0500005b05000000000000,GameSir G3w,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,
03000000ac0500002d02000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,
03000000ac0500004d04000000000000,GameSir G4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000373500002210000000000000,GameSir G7 Pro,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,lefty:a2,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Window,
03000000ac0500001a06000000000000,GameSir T3 2.02,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000373500009410000000000000,GameSir Tegenaria Lite,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:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
030000004c0e00001035000000000000,Gamester,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b11,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows,
@@ -245,8 +246,8 @@
03000000b62500000100000000000000,Gametel GT004 01,a:b3,b:b0,dpdown:b10,dpleft:b9,dpright:b8,dpup:b11,leftshoulder:b4,rightshoulder:b5,start:b7,x:b1,y:b2,platform:Windows,
030000008f0e00001411000000000000,Gamo2 Divaller,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:Windows,
03000000120c0000a857000000000000,Gator Claw,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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:Windows,
03000000c21100000791000000000000,Be1 GC101 Controller 1.03,a:b2,b:b1,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:b3,y:b0,platform:Windows,
03000000c9110000f055000000000000,Be1 GC100XF Controller,a:b0,b:b1,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:b2,y:b3,platform:Windows,
03000000c21100000791000000000000,Nacon GC101 1.03,a:b2,b:b1,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:b3,y:b0,platform:Windows,
03000000c9110000f055000000000000,Nacon GC100XF,a:b0,b:b1,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:b2,y:b3,platform:Windows,
030000008305000009a0000000000000,Genius,a:b0,b:b1,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:b2,y:b3,platform:Windows,
030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,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:b2,y:b3,platform:Windows,
03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,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:b2,y:b3,platform:Windows,
@@ -463,7 +464,7 @@
03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,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:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,
03000000091200004488000000000000,MUSIA PlayStation 2 Input Display,a:b0,b:b2,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b6,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:b11,rightx:a2,righty:a3,start:b5,x:b1,y:b3,platform:Windows,
03000000f70600000100000000000000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Windows,
030000006b140000010c000000000000,Nacon GC 400ES,a:b0,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:b2,y:b3,platform:Windows,
030000006b140000010c000000000000,Nacon GC400ES,a:b0,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:b2,y:b3,platform:Windows,
030000006b1400001106000000000000,Nacon Revolution 3 PS4 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:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Windows,
0300000085320000170d000000000000,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:Windows,
0300000085320000190d000000000000,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:Windows,
@@ -1305,7 +1306,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000632500007a05000001020000,Cosmic Byte Ares Wired Controller,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,
03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,
03000000a306000022f6000011010000,Cyborg V3 Rumble,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:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,
030000005e0400008e02000002010000,Data Frog S80,a:b1,b:b0,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:b3,y:b2,platform:Linux,
03000000791d00000103000010010000,Dual Box Wii Classic Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
030000006f0e00003001000001010000,EA Sports 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:Linux,
03000000c11100000191000011010000,EasySMX,a:b2,b:b1,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:b3,y:b0,platform:Linux,
@@ -1512,9 +1512,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000005e0400008e02000010020000,MSI GC20 V2,a:b0,b:b1,back:b6,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,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,
03000000f70600000100000000010000,N64 Adaptoid,+rightx:b2,+righty:b1,-rightx:b4,-righty:b5,a:b0,b:b3,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,platform:Linux,
030000006b1400000906000014010000,Nacon Asymmetric Wireless PS4 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,
030000006b140000010c000010010000,Nacon GC 400ES,a:b0,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:b2,y:b3,platform:Linux,
03000000853200000706000012010000,Nacon GC-100,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,
05000000853200000503000000010000,Nacon MG-X 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,
030000006b140000010c000010010000,Nacon GC400ES,a:b0,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:b2,y:b3,platform:Linux,
0300000085320000030c000011010000,Nacon GC100,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,
03000000853200000706000012010000,Nacon GC100,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,
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,
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,
@@ -1799,18 +1800,20 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
05000000434f4d4d414e440000000000,VX Gaming Command Series,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
0000000058626f782033363020576900,Xbox 360 Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,
030000005e0400001907000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,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,
030000005e0400008e02000010010000,Xbox 360 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,
030000005e0400008e02000014010000,Xbox 360 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,
030000005e0400009102000007010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,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,
030000005e040000a102000000010000,Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,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,
030000005e040000a102000007010000,Xbox 360 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,
030000005e040000a102000030060000,Xbox 360 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,
030000006f0e00001503000000020000,Xbox 360 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,
030000005e0400008e02000000010000,Xbox 360 EasySMX,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,
030000005e0400008e02000000010000,Xbox 360 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,
030000005e0400008e02000010010000,Xbox 360 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,
030000005e0400008e02000002010000,Xbox 360 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,
030000005e0400008e02000014010000,Xbox 360 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,
030000005e0400008e02000047010000,Xbox 360 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,
030000005e0400008e02000072050000,Xbox 360 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,
030000005e040000a102000014010000,Xbox 360 Receiver,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,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,
0000000058626f782047616d65706100,Xbox 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:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,
030000005e0400000202000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
030000005e0400008e02000072050000,Xbox 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,
030000006f0e00001304000000010000,Xbox 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,
03000000ffff0000ffff000000010000,Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
030000005e0400000a0b000005040000,Xbox One Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,

View File

@@ -112,7 +112,7 @@ struct VS_OUTPUT
struct PS_INPUT
{
float4 p : SV_Position;
noperspective centroid float4 p : SV_Position;
float4 t : TEXCOORD0;
float4 ti : TEXCOORD2;
#if VS_IIP != 0 || GS_IIP != 0 || PS_IIP != 0
@@ -140,7 +140,7 @@ struct PS_OUTPUT
#endif
#endif
#if PS_ZCLAMP || PS_ZFLOOR
float depth : SV_Depth;
float depth : SV_DepthLessEqual;
#endif
};

View File

@@ -113,6 +113,10 @@ layout(binding = 3) uniform sampler2D img_prim_min;
//layout(pixel_center_integer) in vec4 gl_FragCoord;
#endif
#if (PS_ZFLOOR || PS_ZCLAMP) && HAS_CONSERVATIVE_DEPTH
layout(depth_less) out float gl_FragDepth;
#endif
vec4 sample_from_rt()
{
#if !NEEDS_RT

View File

@@ -361,6 +361,10 @@ layout(set = 1, binding = 1) uniform texture2D Palette;
layout(set = 1, binding = 3) uniform texture2D PrimMinTexture;
#endif
#if PS_ZFLOOR || PS_ZCLAMP
layout(depth_less) out float gl_FragDepth;
#endif
#if NEEDS_TEX
vec4 sample_c(vec2 uv)

View File

@@ -184,7 +184,7 @@ if __name__ == "__main__":
args = parser.parse_args()
MAX_DIFF_FRAMES = args.maxframes
outfile = open(args.outfile, "w")
outfile = open(args.outfile, "w", encoding="utf-8")
write(FILE_HEADER)
if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)):

View File

@@ -123,6 +123,11 @@ void BreakpointDialog::accept()
bp->cond.expression = expr;
bp->cond.expressionString = m_ui.txtCondition->text().toStdString();
}
else
{
bp->hasCond = false;
bp->cond = {};
}
}
if (auto* mc = std::get_if<MemCheck>(&m_bp_mc))
{
@@ -159,6 +164,11 @@ void BreakpointDialog::accept()
mc->cond.expression = expr;
mc->cond.expressionString = m_ui.txtCondition->text().toStdString();
}
else
{
mc->hasCond = false;
mc->cond = {};
}
int condition = 0;
if (m_ui.chkRead->isChecked())

View File

@@ -135,7 +135,7 @@
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Options and Patches</string>
<string>Fast Boot Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>

View File

@@ -140,27 +140,34 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
return;
}
// Locate home directory
// Get path to Desktop or per-user Start Menu\Programs directory
// https://superuser.com/questions/1489874/how-can-i-get-the-real-path-of-desktop-in-windows-explorer/1789849#1789849
// https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
// https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
std::string link_file;
if (const char* home = std::getenv("USERPROFILE"))
if (PWSTR directory; SUCCEEDED(SHGetKnownFolderPath(is_desktop ? FOLDERID_Desktop : FOLDERID_Programs, 0, NULL, &directory)))
{
std::string directory_utf8 = StringUtil::WideStringToUTF8String(directory);
CoTaskMemFree(directory);
if (is_desktop)
link_file = Path::ToNativePath(fmt::format("{}/Desktop/{}.lnk", home, clean_name));
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", directory_utf8, clean_name));
else
{
const std::string start_menu_dir = Path::ToNativePath(fmt::format("{}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/PCSX2", home));
if (!FileSystem::EnsureDirectoryExists(start_menu_dir.c_str(), false))
const std::string pcsx2_start_menu_dir = Path::ToNativePath(fmt::format("{}/PCSX2", directory_utf8));
if (!FileSystem::EnsureDirectoryExists(pcsx2_start_menu_dir.c_str(), false))
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Could not create start menu directory."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", start_menu_dir, clean_name));
link_file = Path::ToNativePath(fmt::format("{}/{}.lnk", pcsx2_start_menu_dir, clean_name));
}
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
CoTaskMemFree(directory);
QMessageBox::critical(this, tr("Failed to create shortcut"), is_desktop ? tr("'Desktop' directory not found") : tr("User's 'Start Menu\\Programs' directory not found"), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}

View File

@@ -295,6 +295,9 @@
<property name="text">
<string>Desktop</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>

File diff suppressed because it is too large Load Diff

View File

@@ -830,6 +830,7 @@ set(pcsx2HostHeaders
set(pcsx2ImGuiSources
ImGui/FullscreenUI.cpp
ImGui/FullscreenUI_Settings.cpp
ImGui/ImGuiFullscreen.cpp
ImGui/ImGuiManager.cpp
ImGui/ImGuiOverlays.cpp
@@ -837,6 +838,7 @@ set(pcsx2ImGuiSources
set(pcsx2ImGuiHeaders
ImGui/FullscreenUI.h
ImGui/FullscreenUI_Internal.h
ImGui/ImGuiAnimated.h
ImGui/ImGuiFullscreen.h
ImGui/ImGuiManager.h

View File

@@ -139,12 +139,15 @@ The clamp modes are also numerically based.
### GS Hardware Mipmap Fixes
* mipmap [`0` or `1` or `2`] {Off, Basic, Full} Default: Automatic (No value, looks up GameDB)
* mipmap [`0` or `1`] {Off, On} Default: On (looks up GameDB)
* trilinearFiltering [`0` or `1` or `2`] {None, Trilinear, Trilinear Ultra} Default: None (`0`)
### GS Hardware General Fixes
* beforeDraw {`OI` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name (ex. OI_BurnoutGames)
* moveHandler {`MV` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name (ex. MV_Ico)
* afterDraw {`OO` with suffix } {None unless specific game GSC} Default: Automatic (No value, looks up GameDB) with valid variable name
* conservativeFramebuffer [`0` or `1`] {Off or On} Default: On (`1`)
* texturePreloading [`0` or `1` or `2`] {None, Partial or Full Hash Cache} Default: None (`0`)
@@ -153,11 +156,17 @@ The clamp modes are also numerically based.
### GS Hardware Renderer Fixes
* autoFlush [`0` or `1` or `2`] {Disabled, Enabled (Sprites Only), Enabled (All Primitives)} Default: Off (`0`)
* partialTargetInvalidation [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOffsets [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* disableDepthSupport [`0` or `1`] {Off, On} Default: Off (`0`)
* disablePartialInvalidation [`0` or `1`] {Off, On} Default: Off (`0`)
* cpuFramebufferConversion [`0` or `1`] {Off, On} Default: Off (`0`)
* preloadFrameData [`0` or `1`] {Off, On} Default: Off (`0`)
* textureInsideRT [`0` or `1`] {Disabled, Inside Targets, Merge Targets} Default: Off (`0`)
* textureInsideRT [`0` or `1`or `2`] {Disabled, Inside Targets, Merge Targets} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* PCRTCOverscan [`0` or `1`] {Off, On} Default: Off (`0`)
* cpuCLUTRender [`0` or `1` or `2`] {Disabled, Normal, Aggressive} Default: Disabled (`0`)
* cpuSpriteRenderBW [Value between `0` to `10`] {Disabled, 1 (64), 2 (128), 3 (192), 4 (256), 5 (320), 6 (384), 7 (448), 8 (512), 9 (576), 10 (640)} Default: Off (`0`)

View File

@@ -229,11 +229,6 @@
"minimum": 0,
"maximum": 100000
},
"halfBottomOverride": {
"type": "integer",
"minimum": 0,
"maximum": 1
},
"halfPixelOffset": {
"type": "integer",
"minimum": 0,

File diff suppressed because it is too large Load Diff

View File

@@ -127,41 +127,37 @@ private:
protected:
static constexpr int INVALID_ALPHA_MINMAX = 500;
static constexpr int MAX_DRAW_BUFFERS = 3;
GSVertex m_v = {};
float m_q = 1.0f;
GSVector4i m_scissor_cull_min = {};
GSVector4i m_scissor_cull_max = {};
GSVector4i m_xyof = {};
u32 m_used_buffers_idx = 0;
u32 m_current_buffer_idx = 0;
bool m_recent_buffer_switch = false;
struct GSVertexBuff
struct
{
GSVertex* buff;
GSVertex* buff_copy; // same size buffer to copy/modify the original buffer
GSVertex* buff_copy; // same size buffer to copy/modify the original buffer
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
u32 xy_tail;
GSVector4i xy[4];
GSVector4i xyhead;
};
} m_vertex = {};
GSVertexBuff m_vertex_buffers[MAX_DRAW_BUFFERS];
GSVertexBuff* m_vertex;
struct GSIndexBuff
struct
{
u16* buff;
u32 tail;
};
} m_index = {};
GSIndexBuff m_index_buffers[MAX_DRAW_BUFFERS];
GSIndexBuff* m_index;
GSVertexBuff m_draw_vertex = {};
struct
{
GSVertex* buff;
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
u32 xy_tail;
GSVector4i xy[4];
GSVector4i xyhead;
} m_draw_vertex = {};
struct
{
@@ -169,16 +165,6 @@ protected:
u32 tail;
} m_draw_index = {};
struct GSDrawBufferEnv
{
GSDrawingEnvironment m_env;
int m_backed_up_ctx = 0;
u32 m_dirty_regs = 0;
GSVector4i draw_rect = GSVector4i::zero();
};
GSDrawBufferEnv m_env_buffers[MAX_DRAW_BUFFERS] = {};
void UpdateContext();
void UpdateScissor();
@@ -189,7 +175,6 @@ protected:
template<u32 prim> void HandleAutoFlush();
bool EarlyDetectShuffle(u32 prim);
void CheckCLUTValidity(u32 prim);
bool CheckOverlapVerts(u32 n);
template <u32 prim, bool auto_flush> void VertexKick(u32 skip);
@@ -256,10 +241,9 @@ public:
GSLocalMemory m_mem;
GSDrawingEnvironment m_env = {};
GSDrawingEnvironment m_prev_env = {};
GSDrawingEnvironment m_temp_env = {};
const GSDrawingEnvironment* m_draw_env = &m_env;
GSDrawingContext* m_context = nullptr;
GSVector4i temp_draw_rect;
GSVector4i temp_draw_rect = {};
std::unique_ptr<GSDumpBase> m_dump;
bool m_scissor_invalid = false;
bool m_quad_check_valid = false;
@@ -457,15 +441,7 @@ public:
virtual void Reset(bool hardware_reset);
virtual void UpdateSettings(const Pcsx2Config::GSOptions& old_config);
void ResetDrawBuffers();
void ResetDrawBufferIdx();
void FlushBuffers(bool use_flush_reason = false, GSFlushReason flush_reason = GSFlushReason::CONTEXTCHANGE);
void PushBuffer();
void SetDrawBufferEnv();
void SetDrawBuffDirty();
bool CanBufferNewDraw();
void Flush(GSFlushReason reason);
void FlushDraw(GSFlushReason reason);
u32 CalcMask(int exp, int max_exp);
void FlushPrim();
bool TestDrawChanged();
@@ -478,7 +454,7 @@ public:
virtual void Move();
GSVector4i GetTEX0Rect(GSDrawingContext prev_ctx);
GSVector4i GetTEX0Rect();
void CheckWriteOverlap(bool req_write, bool req_read);
void Write(const u8* mem, int len);
void Read(u8* mem, int len);

View File

@@ -244,7 +244,7 @@ const char* GSDevice::RenderAPIToString(RenderAPI api)
bool GSDevice::GetRequestedExclusiveFullscreenMode(u32* width, u32* height, float* refresh_rate)
{
const std::string mode = Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", "");
const std::string mode = Host::GetStringSettingValue("EmuCore/GS", "FullscreenMode", "");
if (!mode.empty())
{
const std::string_view mode_view = mode;
@@ -899,14 +899,13 @@ void GSDevice::ShadeBoost()
if (ResizeRenderTarget(&m_target_tmp, m_current->GetWidth(), m_current->GetHeight(), false, false))
{
// predivide to avoid the divide (multiply) in the shader
const float params[4] = {
const GSVector4 params(
static_cast<float>(GSConfig.ShadeBoost_Brightness) * (1.0f / 50.0f),
static_cast<float>(GSConfig.ShadeBoost_Contrast) * (1.0f / 50.0f),
static_cast<float>(GSConfig.ShadeBoost_Saturation) * (1.0f / 50.0f),
static_cast<float>(GSConfig.ShadeBoost_Gamma) * (1.0f / 50.0f),
};
static_cast<float>(GSConfig.ShadeBoost_Gamma) * (1.0f / 50.0f));
DoShadeBoost(m_current, m_target_tmp, params);
DoShadeBoost(m_current, m_target_tmp, params.v);
m_current = m_target_tmp;
}

View File

@@ -1397,7 +1397,7 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
const float right = dRect.z * 2 / ds.x - 1.0f;
const float bottom = 1.0f - dRect.w * 2 / ds.y;
GSVertexPT1 vertices[] =
const GSVertexPT1 vertices[] =
{
{GSVector4(left, top, 0.5f, 1.0f), GSVector2(sRect.x, sRect.y)},
{GSVector4(right, top, 0.5f, 1.0f), GSVector2(sRect.z, sRect.y)},
@@ -1427,13 +1427,14 @@ void GSDevice11::PresentRect(GSTexture* sTex, const GSVector4& sRect, GSTexture*
void GSDevice11::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
{
// match merge cb
struct Uniforms
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 offsetX, offsetY, dOffset;
u32 pad2;
};
const Uniforms cb = {sScale, {}, offsetX, offsetY, dOffset};
const Uniforms cb = {sScale, {}, offsetX, offsetY, dOffset, 0};
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
const GSVector4 dRect(0, 0, dSize, 1);
@@ -1444,14 +1445,15 @@ void GSDevice11::UpdateCLUTTexture(GSTexture* sTex, float sScale, u32 offsetX, u
void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
{
// match merge cb
struct Uniforms
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 SBW, DBW, SPSM;
u32 pad2;
};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM, 0};
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
const GSVector4 dRect(0, 0, dTex->GetWidth(), dTex->GetHeight());
@@ -1461,7 +1463,7 @@ void GSDevice11::ConvertToIndexedTexture(GSTexture* sTex, float sScale, u32 offs
void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect)
{
struct Uniforms
struct alignas(16) Uniforms
{
float weight;
float step_multiplier;
@@ -2063,13 +2065,13 @@ void GSDevice11::RenderImGui()
UpdateImGuiTextures();
const float L = 0.0f;
constexpr float L = 0.0f;
const float R = static_cast<float>(m_window_info.surface_width);
const float T = 0.0f;
constexpr float T = 0.0f;
const float B = static_cast<float>(m_window_info.surface_height);
// clang-format off
const float ortho_projection[4][4] =
const GSVector4 ortho_projection[4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },

View File

@@ -193,11 +193,24 @@ void GSDevice12::LoadAgilitySDK()
if (agility_loaded)
return;
HRESULT hr;
// On older versions of Windows 10 (example 2019 LTSC) D3D12GetInterface may fail because it doesn't exist,
// in such case we can check if D3D12GetInterface exists first.
const HMODULE d3d12 = GetModuleHandleW(L"d3d12.dll");
if (!d3d12)
return;
using PFN_D3D12GetInterface = HRESULT(WINAPI*)(REFCLSID rclsid, REFIID riid, void** ppv);
auto pD3D12GetInterface = reinterpret_cast<PFN_D3D12GetInterface>(GetProcAddress(d3d12, "D3D12GetInterface"));
if (!pD3D12GetInterface)
{
Console.Error("D3D12: Agility SDK configuration is not available");
return;
}
// See https://microsoft.github.io/DirectX-Specs/d3d/IndependentDevices.html
ComPtr<ID3D12SDKConfiguration1> sdk_configuration;
hr = D3D12GetInterface(CLSID_D3D12SDKConfiguration, IID_PPV_ARGS(sdk_configuration.put()));
HRESULT hr;
hr = pD3D12GetInterface(CLSID_D3D12SDKConfiguration, IID_PPV_ARGS(sdk_configuration.put()));
if (FAILED(hr))
{
Console.Error("D3D12: Agility SDK configuration is not available");
@@ -925,6 +938,7 @@ void GSDevice12::SetVSyncMode(GSVSyncMode mode, bool allow_present_throttle)
if (GetSwapChainBufferCount() != old_buffer_count)
{
ExecuteCommandList(true);
DestroySwapChain();
if (!CreateSwapChain())
pxFailRel("Failed to recreate swap chain after vsync change.");
@@ -1622,14 +1636,14 @@ void GSDevice12::UpdateCLUTTexture(
GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
{
// match merge cb
struct Uniforms
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 offsetX, offsetY, dOffset;
u32 pad2;
};
const Uniforms cb = {sScale, {}, offsetX, offsetY, dOffset};
const Uniforms cb = {sScale, {}, offsetX, offsetY, dOffset, 0};
SetUtilityRootSignature();
SetUtilityPushConstants(&cb, sizeof(cb));
@@ -1643,14 +1657,15 @@ void GSDevice12::ConvertToIndexedTexture(
GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
{
// match merge cb
struct Uniforms
struct alignas(16) Uniforms
{
float scale;
float pad1[3];
u32 SBW, DBW, SPSM;
u32 pad2;
};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM};
const Uniforms cb = {sScale, {}, SBW, DBW, SPSM, 0};
SetUtilityRootSignature();
SetUtilityPushConstants(&cb, sizeof(cb));
@@ -1662,7 +1677,7 @@ void GSDevice12::ConvertToIndexedTexture(
void GSDevice12::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect)
{
struct Uniforms
struct alignas(16) Uniforms
{
float weight;
float step_multiplier;
@@ -2171,7 +2186,7 @@ void GSDevice12::RenderImGui()
const float B = static_cast<float>(m_window_info.surface_height);
// clang-format off
const float ortho_projection[4][4] =
const GSVector4 ortho_projection[4] =
{
{ 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
{ 0.0f, 2.0f/(T-B), 0.0f, 0.0f },

View File

@@ -115,7 +115,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
// Detect the deswizzling shuffle from depth, copying the RG and BA separately on each half of the page (ignore the split).
if (RTME && RFBP != RTBP0 && RFPSM == PSMCT16S && RTPSM == PSMCT16S)
{
if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index->tail == 128)
if (r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 64 && r.m_index.tail == 128)
{
const GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y/2, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y/2);
const GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y/2, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y/2);
@@ -126,7 +126,7 @@ bool GSHwHack::GSC_IRem(GSRendererHW& r, int& skip)
}
// Following the previous draw, it tries to copy everything read from depth and offset it by 2, for the alternate line channel shuffle (skipped above).
if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index->tail == 2)
if (RTBP0 == (RFBP - 0x20) && r.m_vt.m_max.p.x == 64 && r.m_vt.m_max.p.y == 34 && r.m_index.tail == 2)
{
GSVector4i draw_size(r.m_vt.m_min.p.x, r.m_vt.m_min.p.y - 2.0f, r.m_vt.m_max.p.x, r.m_vt.m_max.p.y - 2.0f);
GSVector4i read_size(r.m_vt.m_min.t.x, r.m_vt.m_min.t.y, r.m_vt.m_max.t.x, r.m_vt.m_max.t.y);
@@ -264,10 +264,10 @@ bool GSHwHack::GSC_SFEX3(GSRendererHW& r, int& skip)
// Skipping is no good as the copy is used again later, and it causes a weird shimmer/echo effect every other frame.
// Add on the height from the second part of the draw to the first, to make it one big rect.
r.m_vertex->buff[1].XYZ.Y += r.m_vertex->buff[r.m_vertex->tail - 1].XYZ.Y - r.m_context->XYOFFSET.OFY;
r.m_vertex->buff[1].V = r.m_vertex->buff[r.m_vertex->tail - 1].V;
r.m_vertex->tail = 2;
r.m_index->tail = 2;
r.m_vertex.buff[1].XYZ.Y += r.m_vertex.buff[r.m_vertex.tail - 1].XYZ.Y - r.m_context->XYOFFSET.OFY;
r.m_vertex.buff[1].V = r.m_vertex.buff[r.m_vertex.tail - 1].V;
r.m_vertex.tail = 2;
r.m_index.tail = 2;
}
}
@@ -332,9 +332,9 @@ bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip)
{
if (skip == 0)
{
if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index->tail > 2)
if (!s_nativeres && r.PRIM->PRIM == GS_SPRITE && RTME && RTEX0.TFX == 1 && RFPSM == RTPSM && RTPSM == PSMCT32 && RFBMSK == 0xFF000000 && r.m_index.tail > 2)
{
GSVertex* v = &r.m_vertex->buff[0];
GSVertex* v = &r.m_vertex.buff[0];
// Don't enable hack on native res.
// Fixes ghosting/blur effect and white lines appearing in stages: Moonfit Wilderness, Acid Rain - caused by upscaling.
// Game copies the framebuffer as individual page rects with slight offsets (like 1/16 of a pixel etc) which doesn't wokr well with upscaling.
@@ -348,7 +348,7 @@ bool GSHwHack::GSC_NamcoGames(GSRendererHW& r, int& skip)
else
{
// Fixes the alignment of the two halves for the heat haze on the temple stage.
for (u32 i = 0; i < r.m_index->tail; i+=2)
for (u32 i = 0; i < r.m_index.tail; i+=2)
{
v[i].XYZ.Y -= 0x8;
}
@@ -676,7 +676,7 @@ bool GSHwHack::GSC_NFSUndercover(GSRendererHW& r, int& skip)
if (RPRIM->TME && Frame.PSM == PSMCT16S && Frame.FBMSK != 0 && Frame.FBW == 10 && Texture.TBW == 1 && Texture.TBP0 == 0x02800 && Texture.PSM == PSMZ16S)
{
GSVertex* v = &r.m_vertex->buff[1];
GSVertex* v = &r.m_vertex.buff[1];
v[0].XYZ.X = static_cast<u16>(RCONTEXT->XYOFFSET.OFX + ((r.m_r.z * 2) << 4));
v[0].XYZ.Y = static_cast<u16>(RCONTEXT->XYOFFSET.OFY + (r.m_r.w << 4));
v[0].U = r.m_r.z << 4;
@@ -687,8 +687,8 @@ bool GSHwHack::GSC_NFSUndercover(GSRendererHW& r, int& skip)
r.m_vt.m_max.p.y = r.m_r.w;
r.m_vt.m_max.t.x = r.m_r.z;
r.m_vt.m_max.t.y = r.m_r.w;
r.m_vertex->head = r.m_vertex->tail = r.m_vertex->next = 2;
r.m_index->tail = 2;
r.m_vertex.head = r.m_vertex.tail = r.m_vertex.next = 2;
r.m_index.tail = 2;
skip = 79;
}
else
@@ -756,7 +756,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
config.ps.channel = ChannelFetch_RGB;
config.colormask.wrgba = 1 | 2 | 4;
r.EndHLEHardwareDraw(false);
src->m_last_draw = r.s_n;
return true;
}
else
@@ -814,6 +814,7 @@ bool GSHwHack::GSC_PolyphonyDigitalGames(GSRendererHW& r, int& skip)
config.ps.channel = ChannelFetch_RED + channel;
config.colormask.wrgba = 8;
r.EndHLEHardwareDraw(false);
dst->m_last_draw = r.s_n;
}
return true;
@@ -840,7 +841,7 @@ bool GSHwHack::GSC_Battlefield2(GSRendererHW& r, int& skip)
if (dst)
{
float dc = r.m_vertex->buff[1].XYZ.Z;
float dc = r.m_vertex.buff[1].XYZ.Z;
g_gs_device->ClearDepth(dst->m_texture, dc * std::exp2(-32.0f));
}
}
@@ -858,9 +859,9 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
if (RPRIM->TME && RTEX0.TW == 3 && RTEX0.TH == 3 && RTEX0.PSM == 0 && RFRAME.FBMSK == 0x00FFFFFF && RFRAME.FBW == 8 && r.PCRTCDisplays.GetResolution().x > 512)
{
// Check we are drawing stripes
for (u32 i = 1; i < r.m_vertex->tail; i+=2)
for (u32 i = 1; i < r.m_vertex.tail; i+=2)
{
int value = (((r.m_vertex->buff[i].XYZ.X - r.m_vertex->buff[i - 1].XYZ.X) + 8) >> 4);
int value = (((r.m_vertex.buff[i].XYZ.X - r.m_vertex.buff[i - 1].XYZ.X) + 8) >> 4);
if (value != 32)
return false;
}
@@ -872,18 +873,18 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
for (int vert = 32; vert < 40; vert+=2)
{
r.m_vertex->buff[vert].XYZ.X = context->XYOFFSET.OFX + (((vert * 16) << 4) - 8);
r.m_vertex->buff[vert].XYZ.Y = context->XYOFFSET.OFY;
r.m_vertex->buff[vert].U = (vert * 16) << 4;
r.m_vertex->buff[vert].V = 0;
r.m_vertex->buff[vert+1].XYZ.X = context->XYOFFSET.OFX + ((((vert * 16) + 32) << 4) - 8);
r.m_vertex->buff[vert+1].XYZ.Y = context->XYOFFSET.OFY + (r.PCRTCDisplays.GetResolution().y << 4) + 8;
r.m_vertex->buff[vert+1].U = ((vert * 16) + 32) << 4;
r.m_vertex->buff[vert+1].V = r.PCRTCDisplays.GetResolution().y << 4;
r.m_vertex.buff[vert].XYZ.X = context->XYOFFSET.OFX + (((vert * 16) << 4) - 8);
r.m_vertex.buff[vert].XYZ.Y = context->XYOFFSET.OFY;
r.m_vertex.buff[vert].U = (vert * 16) << 4;
r.m_vertex.buff[vert].V = 0;
r.m_vertex.buff[vert+1].XYZ.X = context->XYOFFSET.OFX + ((((vert * 16) + 32) << 4) - 8);
r.m_vertex.buff[vert+1].XYZ.Y = context->XYOFFSET.OFY + (r.PCRTCDisplays.GetResolution().y << 4) + 8;
r.m_vertex.buff[vert+1].U = ((vert * 16) + 32) << 4;
r.m_vertex.buff[vert+1].V = r.PCRTCDisplays.GetResolution().y << 4;
}
/*r.m_vertex->head = r.m_vertex->tail = r.m_vertex->next = 2;
r.m_index->tail = 2;*/
/*r.m_vertex.head = r.m_vertex.tail = r.m_vertex.next = 2;
r.m_index.tail = 2;*/
r.m_vt.m_max.p.x = r.m_r.z;
r.m_vt.m_max.p.y = r.m_r.w;
@@ -917,7 +918,7 @@ bool GSHwHack::GSC_BlueTongueGames(GSRendererHW& r, int& skip)
// This is the giant dither-like depth buffer. We need this on the CPU *and* the GPU for textures which are
// rendered on both.
if (context->FRAME.FBW == 8 && r.m_index->tail == 32 && r.PRIM->TME && context->TEX0.TBW == 1)
if (context->FRAME.FBW == 8 && r.m_index.tail == 32 && r.PRIM->TME && context->TEX0.TBW == 1)
{
r.SwPrimRender(r, false, false);
return false;
@@ -957,8 +958,8 @@ bool GSHwHack::GSC_MetalGearSolid3(GSRendererHW& r, int& skip)
GL_INS("OI_MetalGearSolid3(): %x -> %x, %dx%d, subtract %d", RFBP, RFBP + (RFBW / 2), r.m_r.width(), r.m_r.height(),
w_sub);
for (u32 i = 0; i < r.m_vertex->next; i++)
r.m_vertex->buff[i].XYZ.X -= w_sub_fp;
for (u32 i = 0; i < r.m_vertex.next; i++)
r.m_vertex.buff[i].XYZ.X -= w_sub_fp;
// No point adjusting the scissor, it just ends up expanding out anyway.. but we do have to fix up the draw rect.
r.m_r -= GSVector4i(w_sub);
@@ -971,7 +972,7 @@ bool GSHwHack::GSC_Turok(GSRendererHW& r, int& skip)
// Since we can't look in to the future to check this, the options are either rearrange all the pages in a target when the width changes
// (very slow, could break a ton of stuff which stores different things in the alpha channel), or this. I choose this.
if (r.m_index->tail == 6 && RPRIM->PRIM == 4 && !RTME && RFBMSK == 0x00FFFFFF && floor(r.m_vt.m_max.p.x) == 512 && r.m_env.CTXT[r.m_backed_up_ctx].FRAME.FBW == 10 && RFRAME.FBW == 8 && RFPSM == PSMCT32 && RTEST.ATE && RTEST.ATST == ATST_GEQUAL)
if (r.m_index.tail == 6 && RPRIM->PRIM == 4 && !RTME && RFBMSK == 0x00FFFFFF && floor(r.m_vt.m_max.p.x) == 512 && r.m_env.CTXT[r.m_backed_up_ctx].FRAME.FBW == 10 && RFRAME.FBW == 8 && RFPSM == PSMCT32 && RTEST.ATE && RTEST.ATST == ATST_GEQUAL)
{
int num_pages = r.m_cached_ctx.FRAME.FBW * ((floor(r.m_vt.m_max.p.y) + 31) / 32);
r.m_cached_ctx.FRAME.FBW = 10;
@@ -988,7 +989,7 @@ bool GSHwHack::GSC_Turok(GSRendererHW& r, int& skip)
bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t)
{
const u32 n_vertices = r.m_vertex->next;
const u32 n_vertices = r.m_vertex.next;
const int w = r.m_r.width();
const int h = r.m_r.height();
const bool is_copy = !r.PRIM->ABE || (
@@ -1021,7 +1022,7 @@ bool GSHwHack::OI_PointListPalette(GSRendererHW& r, GSTexture* rt, GSTexture* ds
const u32 FBP = r.m_cached_ctx.FRAME.Block();
const u32 FBW = r.m_cached_ctx.FRAME.FBW;
GL_INS("PointListPalette - m_r = <%d, %d => %d, %d>, n_vertices = %u, FBP = 0x%x, FBW = %u", r.m_r.x, r.m_r.y, r.m_r.z, r.m_r.w, n_vertices, FBP, FBW);
const GSVertex* RESTRICT v = r.m_vertex->buff;
const GSVertex* RESTRICT v = r.m_vertex.buff;
const int ox(r.m_context->XYOFFSET.OFX);
const int oy(r.m_context->XYOFFSET.OFY);
for (size_t i = 0; i < n_vertices; ++i)
@@ -1219,9 +1220,9 @@ bool GSHwHack::OI_ArTonelico2(GSRendererHW& r, GSTexture* rt, GSTexture* ds, GST
buffer to adapt the page width properly.
*/
const GSVertex* v = &r.m_vertex->buff[0];
const GSVertex* v = &r.m_vertex.buff[0];
if (ds && r.m_vertex->next == 2 && !RPRIM->TME && RFRAME.FBW == 10 && v->XYZ.Z == 0 && RTEST.ZTST == ZTST_ALWAYS)
if (ds && r.m_vertex.next == 2 && !RPRIM->TME && RFRAME.FBW == 10 && v->XYZ.Z == 0 && RTEST.ZTST == ZTST_ALWAYS)
{
GL_INS("OI_ArTonelico2");
g_gs_device->ClearDepth(ds, 0.0f);

View File

@@ -218,7 +218,7 @@ void GSRendererHW::Lines2Sprites()
// each sprite converted to quad needs twice the space
while (m_vertex->tail * 2 > m_vertex->maxcount)
while (m_vertex.tail * 2 > m_vertex.maxcount)
{
GrowVertexBuffer();
}
@@ -226,14 +226,14 @@ void GSRendererHW::Lines2Sprites()
// assume vertices are tightly packed and sequentially indexed (it should be the case)
const bool predivide_q = PRIM->TME && !PRIM->FST && m_vt.m_accurate_stq;
if (m_vertex->next >= 2)
if (m_vertex.next >= 2)
{
const u32 count = m_vertex->next;
const u32 count = m_vertex.next;
int i = static_cast<int>(count) * 2 - 4;
GSVertex* s = &m_vertex->buff[count - 2];
GSVertex* q = &m_vertex->buff[count * 2 - 4];
u16* RESTRICT index = &m_index->buff[count * 3 - 6];
GSVertex* s = &m_vertex.buff[count - 2];
GSVertex* q = &m_vertex.buff[count * 2 - 4];
u16* RESTRICT index = &m_index.buff[count * 3 - 6];
// Sprites are flat shaded, so the provoking vertex doesn't matter here.
constexpr GSVector4i indices = GSVector4i::cxpr16(0, 1, 2, 1, 2, 3, 0, 0);
@@ -287,19 +287,19 @@ void GSRendererHW::Lines2Sprites()
std::memcpy(&index[4], &high, sizeof(high));
}
m_vertex->head = m_vertex->tail = m_vertex->next = count * 2;
m_index->tail = count * 3;
m_vertex.head = m_vertex.tail = m_vertex.next = count * 2;
m_index.tail = count * 3;
}
}
void GSRendererHW::ExpandLineIndices()
{
const u32 process_count = (m_index->tail + 7) / 8 * 8;
const u32 process_count = (m_index.tail + 7) / 8 * 8;
constexpr u32 expansion_factor = 3;
m_index->tail *= expansion_factor;
GSVector4i* end = reinterpret_cast<GSVector4i*>(m_index->buff);
GSVector4i* read = reinterpret_cast<GSVector4i*>(m_index->buff + process_count);
GSVector4i* write = reinterpret_cast<GSVector4i*>(m_index->buff + process_count * expansion_factor);
m_index.tail *= expansion_factor;
GSVector4i* end = reinterpret_cast<GSVector4i*>(m_index.buff);
GSVector4i* read = reinterpret_cast<GSVector4i*>(m_index.buff + process_count);
GSVector4i* write = reinterpret_cast<GSVector4i*>(m_index.buff + process_count * expansion_factor);
constexpr GSVector4i mask0 = GSVector4i::cxpr8(0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 2, 3, 4, 5, 4, 5);
constexpr GSVector4i mask1 = GSVector4i::cxpr8(6, 7, 4, 5, 6, 7, 6, 7, 8, 9, 8, 9, 10, 11, 8, 9);
@@ -353,13 +353,13 @@ __fi bool GSRendererHW::Is8PixelReverseSprite(const GSVertex& v0, const GSVertex
// Fix the vertex position/tex_coordinate from 16 bits color to 32 bits color
void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba, bool& shuffle_across, GSTextureCache::Target* rt, GSTextureCache::Source* tex)
{
pxAssert(m_vertex->next % 2 == 0); // Either sprites or an even number of triangles.
pxAssert(m_vertex.next % 2 == 0); // Either sprites or an even number of triangles.
const bool recursive_draw = m_cached_ctx.FRAME.Block() == m_cached_ctx.TEX0.TBP0;
const bool sprites = m_vt.m_primclass == GS_SPRITE_CLASS;
u32 count = m_vertex->next;
GSVertex* v = &m_vertex->buff[0];
u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
const GIFRegXYOFFSET& o = m_context->XYOFFSET;
// Could be drawing upside down or just back to front on the actual verts.
// Iterate through the sprites in order and find one to infer which channels are being shuffled.
@@ -500,12 +500,12 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
// no need to adjust v[0] because it should already be correct.
if (PRIM->FST)
{
v[1].U = v[m_index->buff[m_index->tail - 1]].U;
v[1].V = v[m_index->buff[m_index->tail - 1]].V;
v[1].U = v[m_index.buff[m_index.tail - 1]].U;
v[1].V = v[m_index.buff[m_index.tail - 1]].V;
}
else
{
v[1].ST = v[m_index->buff[m_index->tail - 1]].ST;
v[1].ST = v[m_index.buff[m_index.tail - 1]].ST;
}
}
else
@@ -526,8 +526,8 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
}
}
m_r = r;
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
return;
}
@@ -640,8 +640,8 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
}
if (wi != count)
{
count = m_vertex->head = m_vertex->tail = m_vertex->next = wi;
m_index->tail = wi;
count = m_vertex.head = m_vertex.tail = m_vertex.next = wi;
m_index.tail = wi;
}
}
@@ -805,7 +805,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
}
}
if (m_index->tail == 0)
if (m_index.tail == 0)
{
GL_INS("HW: ConvertSpriteTextureShuffle: Culled all vertices; exiting.");
return;
@@ -896,17 +896,17 @@ void GSRendererHW::ConvertSpriteTextureShuffle(u32& process_rg, u32& process_ba,
m_context->scissor.in.w /= 2;
m_context->scissor.in.z *= 2;
v[1].XYZ.X = ((v[m_index->buff[m_index->tail - 1]].XYZ.X - m_context->XYOFFSET.OFX) * 2) + m_context->XYOFFSET.OFX;
v[1].XYZ.Y = ((v[m_index->buff[m_index->tail - 1]].XYZ.Y - m_context->XYOFFSET.OFY) / 2) + m_context->XYOFFSET.OFY;
v[1].XYZ.X = ((v[m_index.buff[m_index.tail - 1]].XYZ.X - m_context->XYOFFSET.OFX) * 2) + m_context->XYOFFSET.OFX;
v[1].XYZ.Y = ((v[m_index.buff[m_index.tail - 1]].XYZ.Y - m_context->XYOFFSET.OFY) / 2) + m_context->XYOFFSET.OFY;
v[1].U = v[m_index->buff[m_index->tail - 1]].U * 2;
v[1].V = v[m_index->buff[m_index->tail - 1]].V / 2;
v[1].U = v[m_index.buff[m_index.tail - 1]].U * 2;
v[1].V = v[m_index.buff[m_index.tail - 1]].V / 2;
v[1].ST.S = v[m_index->buff[m_index->tail - 1]].ST.S * 2;
v[1].ST.T = v[m_index->buff[m_index->tail - 1]].ST.T / 2;
v[1].ST.S = v[m_index.buff[m_index.tail - 1]].ST.S * 2;
v[1].ST.T = v[m_index.buff[m_index.tail - 1]].ST.T / 2;
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
m_cached_ctx.TEX0.TBW *= 2;
m_cached_ctx.FRAME.FBW *= 2;
@@ -923,7 +923,7 @@ GSVector4 GSRendererHW::RealignTargetTextureCoordinate(const GSTextureCache::Sou
return GSVector4(0.0f);
}
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const float scale = tex->GetScale();
const bool linear = m_vt.IsRealLinear();
const int t_position = v[0].U;
@@ -1009,7 +1009,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
// neither in a fast way. So instead let's just take the hypothesis that all sprites must have the same
// size.
// Tested on Tekken 5.
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
bool is_paving = true;
bool is_paving_h = true;
bool is_paving_v = true;
@@ -1018,7 +1018,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
const int first_dpU = v[1].U - v[0].U;
const int first_dpY = v[1].XYZ.Y - v[0].XYZ.Y;
const int first_dpV = v[1].V - v[0].V;
for (u32 i = 0; i < m_vertex->next; i += 2)
for (u32 i = 0; i < m_vertex.next; i += 2)
{
const int dpX = v[i + 1].XYZ.X - v[i].XYZ.X;
const int dpU = v[i + 1].U - v[i].U;
@@ -1043,14 +1043,14 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
const GSVector4 delta_p = m_vt.m_max.p - m_vt.m_min.p;
const GSVector4 delta_t = m_vt.m_max.t - m_vt.m_min.t;
const bool is_blit = PrimitiveOverlap() == PRIM_OVERLAP_NO;
GL_INS("HW: PP SAMPLER: Dp %f %f Dt %f %f. Is blit %d, is paving %d, count %d", delta_p.x, delta_p.y, delta_t.x, delta_t.y, is_blit, is_paving, m_vertex->tail);
GL_INS("HW: PP SAMPLER: Dp %f %f Dt %f %f. Is blit %d, is paving %d, count %d", delta_p.x, delta_p.y, delta_t.x, delta_t.y, is_blit, is_paving, m_vertex.tail);
#endif
if (is_paving)
{
// Replace all sprite with a single fullscreen sprite.
u32 unique_verts = 2;
GSVertex* s = &m_vertex->buff[0];
GSVertex* s = &m_vertex.buff[0];
if (is_paving_h)
{
s[0].XYZ.X = static_cast<u16>((16.0f * m_vt.m_min.p.x) + m_context->XYOFFSET.OFX);
@@ -1061,7 +1061,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
else
{
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
for (u32 i = 2; i < (m_vertex.tail & ~1); i++)
{
bool unique_found = false;
@@ -1102,7 +1102,7 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
else
{
for (u32 i = 2; i < (m_vertex->tail & ~1); i++)
for (u32 i = 2; i < (m_vertex.tail & ~1); i++)
{
bool unique_found = false;
@@ -1133,8 +1133,8 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
}
}
m_vertex->head = m_vertex->tail = m_vertex->next = unique_verts;
m_index->tail = unique_verts;
m_vertex.head = m_vertex.tail = m_vertex.next = unique_verts;
m_index.tail = unique_verts;
}
}
}
@@ -1261,7 +1261,7 @@ bool GSRendererHW::IsPossibleChannelShuffle() const
{
if (!PRIM->TME || m_cached_ctx.TEX0.PSM != PSMT8 || // 8-bit texture draw
m_vt.m_primclass != GS_SPRITE_CLASS || // draw_sprite_tex
(m_vertex->tail <= 2 && (((m_vt.m_max.p - m_vt.m_min.p) <= GSVector4(8.0f)).mask() & 0x3) == 0x3)) // Powerdrome does a tiny shuffle on a couple of pixels, can't reliably translate this.
(m_vertex.tail <= 2 && (((m_vt.m_max.p - m_vt.m_min.p) <= GSVector4(8.0f)).mask() & 0x3) == 0x3)) // Powerdrome does a tiny shuffle on a couple of pixels, can't reliably translate this.
{
return false;
}
@@ -1269,7 +1269,7 @@ bool GSRendererHW::IsPossibleChannelShuffle() const
const int mask = (((m_vt.m_max.p - m_vt.m_min.p) <= GSVector4(64.0f)).mask() & 0x3);
if (mask == 0x3) // single_page
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const int draw_width = std::abs(v[1].XYZ.X - v[0].XYZ.X) >> 4;
const int draw_height = std::abs(v[1].XYZ.Y - v[0].XYZ.Y) >> 4;
@@ -1291,7 +1291,7 @@ bool GSRendererHW::IsPossibleChannelShuffle() const
if (m_cached_ctx.TEX0.TBW == (m_cached_ctx.FRAME.FBW * 2) &&
GSLocalMemory::IsPageAligned(m_cached_ctx.FRAME.PSM, GSVector4i(m_vt.m_min.p.upld(m_vt.m_max.p))))
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const int draw_width = std::abs(v[1].XYZ.X - v[0].XYZ.X) >> 4;
const int draw_height = std::abs(v[1].XYZ.Y - v[0].XYZ.Y) >> 4;
@@ -1357,7 +1357,7 @@ bool GSRendererHW::IsSplitTextureShuffle(GIFRegTEX0& rt_TEX0, GSVector4i& valid_
return false;
// Different channel being shuffled, so needs to be handled separately (misdetection in 50 Cent)
if (m_vertex->buff[m_index->buff[0]].U != m_v.U)
if (m_vertex.buff[m_index.buff[0]].U != m_v.U)
return false;
// Check that both the position and texture coordinates are page aligned, so we can work in pages instead of coordinates.
@@ -1745,7 +1745,7 @@ bool GSRendererHW::IsDepthAlwaysPassing()
// Depth is always pass/fail (no read) and write are discarded.
return (!m_cached_ctx.TEST.ZTE || m_cached_ctx.TEST.ZTST <= ZTST_ALWAYS) ||
// Depth test will always pass
(m_cached_ctx.TEST.ZTST == ZTST_GEQUAL && m_vt.m_eq.z && std::min(m_vertex->buff[check_index].XYZ.Z, max_z) == max_z);
(m_cached_ctx.TEST.ZTST == ZTST_GEQUAL && m_vt.m_eq.z && std::min(m_vertex.buff[check_index].XYZ.Z, max_z) == max_z);
}
bool GSRendererHW::IsUsingCsInBlend()
@@ -1784,7 +1784,7 @@ bool GSRendererHW::IsTBPFrameOrZ(u32 tbp, bool frame_only)
// Depth is always pass/fail (no read) and write are discarded.
(zm != 0 && m_cached_ctx.TEST.ZTST <= ZTST_ALWAYS) ||
// Depth test will always pass
(zm != 0 && m_cached_ctx.TEST.ZTST == ZTST_GEQUAL && m_vt.m_eq.z && std::min(m_vertex->buff[0].XYZ.Z, max_z) == max_z) ||
(zm != 0 && m_cached_ctx.TEST.ZTST == ZTST_GEQUAL && m_vt.m_eq.z && std::min(m_vertex.buff[0].XYZ.Z, max_z) == max_z) ||
// Depth will be written through the RT
(!no_rt && m_cached_ctx.FRAME.FBP == m_cached_ctx.ZBUF.ZBP && !PRIM->TME && zm == 0 && (fm & fm_mask) == 0 && m_cached_ctx.TEST.ZTE)) ||
// No color or Z being written.
@@ -1801,17 +1801,17 @@ void GSRendererHW::HandleManualDeswizzle()
// Check if it's doing manual deswizzling first (draws are 32x16), if they are, check if the Z is flat, if not,
// we're gonna have to get creative and swap around the quandrants, but that's a TODO.
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &m_vertex.buff[0];
// Check for page quadrant and compare it to the quadrant from the verts, if it does match then we need to do correction.
const GSVector2i page_quadrant = GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].pgs / 2;
if (PRIM->FST)
{
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < m_index.tail; i += 2)
{
const u32 index_first = m_index->buff[i];
const u32 index_last = m_index->buff[i + 1];
const u32 index_first = m_index.buff[i];
const u32 index_last = m_index.buff[i + 1];
if ((abs((v[index_last].U) - (v[index_first].U)) >> 4) != page_quadrant.x || (abs((v[index_last].V) - (v[index_first].V)) >> 4) != page_quadrant.y)
return;
@@ -1819,10 +1819,10 @@ void GSRendererHW::HandleManualDeswizzle()
}
else
{
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < m_index.tail; i += 2)
{
const u32 index_first = m_index->buff[i];
const u32 index_last = m_index->buff[i + 1];
const u32 index_first = m_index.buff[i];
const u32 index_last = m_index.buff[i + 1];
const u32 x = abs(((v[index_last].ST.S / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TW)) - ((v[index_first].ST.S / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TW)));
const u32 y = abs(((v[index_last].ST.T / v[index_last].RGBAQ.Q) * (1 << m_context->TEX0.TH)) - ((v[index_first].ST.T / v[index_first].RGBAQ.Q) * (1 << m_context->TEX0.TH)));
@@ -2027,7 +2027,7 @@ void GSRendererHW::SwSpriteRender()
const bool alpha_blending_enabled = NeedsBlending();
const GSVertex& v = m_index->tail > 0 ? m_vertex->buff[m_index->buff[m_index->tail - 1]] : GSVertex(); // Last vertex if any.
const GSVertex& v = m_index.tail > 0 ? m_vertex.buff[m_index.buff[m_index.tail - 1]] : GSVertex(); // Last vertex if any.
const GSVector4i vc = GSVector4i(v.RGBAQ.R, v.RGBAQ.G, v.RGBAQ.B, v.RGBAQ.A) // 0x000000AA000000BB000000GG000000RR
.ps32(); // 0x00AA00BB00GG00RR00AA00BB00GG00RR
@@ -2156,10 +2156,10 @@ bool GSRendererHW::CanUseSwSpriteRender()
return false;
if (PRIM->PRIM != GS_TRIANGLESTRIP && PRIM->PRIM != GS_SPRITE) // Triangle strip or sprite draw
return false;
if (m_vt.m_primclass == GS_TRIANGLE_CLASS && (PRIM->PRIM != GS_TRIANGLESTRIP || m_vertex->tail != 4)) // If triangle class, strip draw with 4 vertices (two prims, emulating single sprite prim)
if (m_vt.m_primclass == GS_TRIANGLE_CLASS && (PRIM->PRIM != GS_TRIANGLESTRIP || m_vertex.tail != 4)) // If triangle class, strip draw with 4 vertices (two prims, emulating single sprite prim)
return false;
// TODO If GS_TRIANGLESTRIP draw, check that the draw is axis aligned
if (m_vt.m_primclass == GS_SPRITE_CLASS && (PRIM->PRIM != GS_SPRITE || m_vertex->tail != 2)) // If sprite class, sprite draw with 2 vertices (one prim)
if (m_vt.m_primclass == GS_SPRITE_CLASS && (PRIM->PRIM != GS_SPRITE || m_vertex.tail != 2)) // If sprite class, sprite draw with 2 vertices (one prim)
return false;
if (m_cached_ctx.DepthRead() || m_cached_ctx.DepthWrite()) // No depth handling
return false;
@@ -2203,8 +2203,8 @@ void GSRendererHW::RoundSpriteOffset()
#if defined(DEBUG_V) || defined(DEBUG_U)
bool debug = linear;
#endif
const u32 count = m_vertex->next;
GSVertex* v = &m_vertex->buff[0];
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
for (u32 i = 0; i < count; i += 2)
{
@@ -2333,7 +2333,7 @@ void GSRendererHW::Draw()
m_cached_ctx.FRAME = context->FRAME;
m_cached_ctx.ZBUF = context->ZBUF;
if (IsBadFrame())
if (IsBadFrame())
{
GL_INS("HW: Warning skipping a draw call (%d)", s_n);
return;
@@ -2807,7 +2807,7 @@ void GSRendererHW::Draw()
}
const u32 vert_index = (m_vt.m_primclass == GS_TRIANGLE_CLASS) ? 2 : 1;
u32 const_color = m_vertex->buff[m_index->buff[vert_index]].RGBAQ.U32[0];
u32 const_color = m_vertex.buff[m_index.buff[vert_index]].RGBAQ.U32[0];
u32 fb_mask = m_cached_ctx.FRAME.FBMSK;
// If we could just check the colour, it would be great, but Echo Night decided it's going to set the alpha and green to 128, for some reason, and actually be 32bit, so it ruined my day.
@@ -2843,7 +2843,7 @@ void GSRendererHW::Draw()
m_cached_ctx.TEXA.TA0 = 0;
m_cached_ctx.TEXA.TA1 = 128;
m_cached_ctx.FRAME.PSM = (m_cached_ctx.FRAME.PSM & 2) ? m_cached_ctx.FRAME.PSM : PSMCT16;
m_vertex->buff[m_index->buff[1]].RGBAQ.U32[0] = const_color;
m_vertex.buff[m_index.buff[1]].RGBAQ.U32[0] = const_color;
ReplaceVerticesWithSprite(m_r, GSVector2i(m_r.width(), m_r.height()));
}
@@ -3060,13 +3060,13 @@ void GSRendererHW::Draw()
const u32 page_alignment = GSLocalMemory::IsPageAlignedMasked(m_cached_ctx.TEX0.PSM, m_r);
const bool page_aligned = (page_alignment & 0xF0F0) != 0; // Make sure Y is page aligned.
if (!no_rt && page_aligned && m_cached_ctx.ZBUF.ZMSK && GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
(m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index->tail % 6) == 0 && TrianglesAreQuads(true) && m_index->tail > 6)))
(m_vt.m_primclass == GS_SPRITE_CLASS || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true) && m_index.tail > 6)))
{
// Tail check is to make sure we have enough strips to go all the way across the page, or if it's using a region clamp could be used to draw strips.
if (GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp == 16 &&
(m_index->tail >= (m_cached_ctx.TEX0.TBW * 2) || m_cached_ctx.TEX0.TBP0 == m_cached_ctx.FRAME.Block() || m_cached_ctx.CLAMP.WMS > CLAMP_CLAMP || m_cached_ctx.CLAMP.WMT > CLAMP_CLAMP))
(m_index.tail >= (m_cached_ctx.TEX0.TBW * 2) || m_cached_ctx.TEX0.TBP0 == m_cached_ctx.FRAME.Block() || m_cached_ctx.CLAMP.WMS > CLAMP_CLAMP || m_cached_ctx.CLAMP.WMT > CLAMP_CLAMP))
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const int first_x = std::clamp((static_cast<int>(((v[0].XYZ.X - m_context->XYOFFSET.OFX) + 8))) >> 4, 0, 2048);
const bool offset_last = PRIM->FST ? (v[1].U > v[0].U) : ((v[1].ST.S / v[1].RGBAQ.Q) > (v[0].ST.S / v[1].RGBAQ.Q));
@@ -3092,11 +3092,11 @@ void GSRendererHW::Draw()
{
bool shuffle_channel_reads = !m_cached_ctx.FRAME.FBMSK;
const u32 increment = (m_vt.m_primclass == GS_TRIANGLE_CLASS) ? 3 : 2;
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
if (shuffle_channel_reads)
{
for (u32 i = 0; i < m_index->tail; i += increment)
for (u32 i = 0; i < m_index.tail; i += increment)
{
const int first_u = (PRIM->FST ? v[i].U : static_cast<int>(v[i].ST.S / v[(increment == 2) ? i + 1 : i].RGBAQ.Q)) >> 4;
const int second_u = (PRIM->FST ? v[i + 1].U : static_cast<int>(v[i + 1].ST.S / v[i + 1].RGBAQ.Q)) >> 4;
@@ -3243,6 +3243,7 @@ void GSRendererHW::Draw()
float target_scale = GetTextureScaleFactor();
bool scaled_copy = false;
int scale_draw = IsScalingDraw(src, m_primitive_covers_without_gaps != NoGapsType::GapsFound);
m_downscale_source = false;
if (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off)
{
@@ -3276,8 +3277,6 @@ void GSRendererHW::Draw()
scale_draw = 1;
scaled_copy = true;
}
m_downscale_source = false;
}
}
@@ -3446,9 +3445,9 @@ void GSRendererHW::Draw()
if (vertical_offset || horizontal_offset)
{
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &m_vertex.buff[0];
for (u32 i = 0; i < m_vertex->tail; i++)
for (u32 i = 0; i < m_vertex.tail; i++)
{
v[i].XYZ.X += horizontal_offset << 4;
v[i].XYZ.Y += vertical_offset << 4;
@@ -3743,9 +3742,9 @@ void GSRendererHW::Draw()
if (vertical_offset || horizontal_offset)
{
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &m_vertex.buff[0];
for (u32 i = 0; i < m_vertex->tail; i++)
for (u32 i = 0; i < m_vertex.tail; i++)
{
v[i].XYZ.X += horizontal_offset << 4;
v[i].XYZ.Y += vertical_offset << 4;
@@ -4032,7 +4031,7 @@ void GSRendererHW::Draw()
if (m_process_texture)
{
GIFRegCLAMP MIP_CLAMP = m_cached_ctx.CLAMP;
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
if (rt)
{
@@ -4054,7 +4053,7 @@ void GSRendererHW::Draw()
// Both input and output are 16 bits and texture was initially 32 bits! Same for the target, Sonic Unleash makes a new target which really is 16bit.
m_texture_shuffle = ((m_same_group_texture_shuffle || (tex_psm.bpp == 16)) && (GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].bpp == 16) && (shuffle_coords || rt->m_32_bits_fmt)) &&
(src->m_32_bits_fmt || m_copy_16bit_to_target_shuffle) &&
(draw_sprite_tex || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index->tail % 6) == 0 && TrianglesAreQuads(true)));
(draw_sprite_tex || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads(true)));
if (m_texture_shuffle && IsSplitTextureShuffle(rt->m_TEX0, rt->m_valid))
{
@@ -4222,13 +4221,13 @@ void GSRendererHW::Draw()
if (!m_texture_shuffle && !m_channel_shuffle)
{
// Try to turn blits in to single sprites, saves upscaling problems when striped clears/blits.
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_primitive_covers_without_gaps == NoGapsType::FullCover && m_index->tail > 2 && (!PRIM->TME || TextureCoversWithoutGapsNotEqual()) && m_vt.m_eq.rgba == 0xFFFF)
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_primitive_covers_without_gaps == NoGapsType::FullCover && m_index.tail > 2 && (!PRIM->TME || TextureCoversWithoutGapsNotEqual()) && m_vt.m_eq.rgba == 0xFFFF)
{
// Full final framebuffer only.
const GSVector2i fb_size = PCRTCDisplays.GetFramebufferSize(-1);
if (std::abs(fb_size.x - m_r.width()) <= 1 && std::abs(fb_size.y - m_r.height()) <= 1)
{
GSVertex* v = m_vertex->buff;
GSVertex* v = m_vertex.buff;
v[0].XYZ.Z = v[1].XYZ.Z;
v[0].RGBAQ = v[1].RGBAQ;
@@ -4237,23 +4236,23 @@ void GSRendererHW::Draw()
m_vt.m_eq.z = true;
m_vt.m_eq.f = true;
v[1].XYZ.X = v[m_index->tail - 1].XYZ.X;
v[1].XYZ.Y = v[m_index->tail - 1].XYZ.Y;
v[1].XYZ.X = v[m_index.tail - 1].XYZ.X;
v[1].XYZ.Y = v[m_index.tail - 1].XYZ.Y;
if (PRIM->FST)
{
v[1].U = v[m_index->tail - 1].U;
v[1].V = v[m_index->tail - 1].V;
v[1].U = v[m_index.tail - 1].U;
v[1].V = v[m_index.tail - 1].V;
}
else
{
v[1].ST.S = v[m_index->tail - 1].ST.S;
v[1].ST.T = v[m_index->tail - 1].ST.T;
v[1].RGBAQ.Q = v[m_index->tail - 1].RGBAQ.Q;
v[1].ST.S = v[m_index.tail - 1].ST.S;
v[1].ST.T = v[m_index.tail - 1].ST.T;
v[1].RGBAQ.Q = v[m_index.tail - 1].RGBAQ.Q;
}
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
}
}
@@ -4718,8 +4717,8 @@ void GSRendererHW::Draw()
// but it still needs to adjust native stuff from memory as it's not been compensated for upscaling (Dragon Quest 8 font for example).
if (CanUpscale() && (m_vt.m_primclass == GS_SPRITE_CLASS) && rt && rt->GetScale() > 1.0f)
{
const u32 count = m_vertex->next;
GSVertex* v = &m_vertex->buff[0];
const u32 count = m_vertex.next;
GSVertex* v = &m_vertex.buff[0];
// Hack to avoid vertical black line in various games (ace combat/tekken)
if (GSConfig.UserHacks_AlignSpriteX)
@@ -4945,30 +4944,30 @@ bool GSRendererHW::VerifyIndices()
switch (m_vt.m_primclass)
{
case GS_SPRITE_CLASS:
if (m_index->tail % 2 != 0)
if (m_index.tail % 2 != 0)
return false;
[[fallthrough]];
case GS_POINT_CLASS:
// Expect indices to be flat increasing
for (u32 i = 0; i < m_index->tail; i++)
for (u32 i = 0; i < m_index.tail; i++)
{
if (m_index->buff[i] != i)
if (m_index.buff[i] != i)
return false;
}
break;
case GS_LINE_CLASS:
if (m_index->tail % 2 != 0)
if (m_index.tail % 2 != 0)
return false;
// Expect each line to be a pair next to each other
// VS expand relies on this!
for (u32 i = 0; i < m_index->tail; i += 2)
for (u32 i = 0; i < m_index.tail; i += 2)
{
if (m_index->buff[i] + 1 != m_index->buff[i + 1])
if (m_index.buff[i] + 1 != m_index.buff[i + 1])
return false;
}
break;
case GS_TRIANGLE_CLASS:
if (m_index->tail % 3 != 0)
if (m_index.tail % 3 != 0)
return false;
break;
case GS_INVALID_CLASS:
@@ -4993,9 +4992,9 @@ void GSRendererHW::HandleProvokingVertexFirst()
// If all first/last vertices have the same color there is nothing to do.
bool first_eq_last = true;
for (u32 i = 0; i < m_index->tail; i += n)
for (u32 i = 0; i < m_index.tail; i += n)
{
if (m_vertex->buff[m_index->buff[i]].RGBAQ.U32[0] != m_vertex->buff[m_index->buff[i + n - 1]].RGBAQ.U32[0])
if (m_vertex.buff[m_index.buff[i]].RGBAQ.U32[0] != m_vertex.buff[m_index.buff[i + n - 1]].RGBAQ.U32[0])
{
first_eq_last = false;
break;
@@ -5005,21 +5004,21 @@ void GSRendererHW::HandleProvokingVertexFirst()
return;
// De-index the vertices using the copy buffer
while (m_vertex->maxcount < m_index->tail)
while (m_vertex.maxcount < m_index.tail)
GrowVertexBuffer();
for (int i = static_cast<int>(m_index->tail) - 1; i >= 0; i--)
for (int i = static_cast<int>(m_index.tail) - 1; i >= 0; i--)
{
m_vertex->buff_copy[i] = m_vertex->buff[m_index->buff[i]];
m_index->buff[i] = static_cast<u16>(i);
m_vertex.buff_copy[i] = m_vertex.buff[m_index.buff[i]];
m_index.buff[i] = static_cast<u16>(i);
}
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
std::swap(m_vertex.buff, m_vertex.buff_copy);
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;
// Put correct color in the first vertex
for (u32 i = 0; i < m_index->tail; i += n)
for (u32 i = 0; i < m_index.tail; i += n)
{
m_vertex->buff[i].RGBAQ.U32[0] = m_vertex->buff[i + n - 1].RGBAQ.U32[0];
m_vertex->buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
m_vertex.buff[i].RGBAQ.U32[0] = m_vertex.buff[i + n - 1].RGBAQ.U32[0];
m_vertex.buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
}
}
@@ -5029,8 +5028,8 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
if (GSConfig.UserHacks_ForceEvenSpritePosition && !m_isPackedUV_HackFlag && m_process_texture && PRIM->FST)
{
for (u32 i = 0; i < m_vertex->next; i++)
m_vertex->buff[i].UV &= 0x3FEF3FEF;
for (u32 i = 0; i < m_vertex.next; i++)
m_vertex.buff[i].UV &= 0x3FEF3FEF;
}
const bool unscale_pt_ln = !GSConfig.UserHacks_DisableSafeFeatures && (target_scale != 1.0f);
@@ -5056,9 +5055,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
m_conf.vs.expand = GSHWDrawConfig::VSExpand::Point;
m_conf.cb_vs.point_size = GSVector2(16.0f * sx, 16.0f * sy);
m_conf.topology = GSHWDrawConfig::Topology::Triangle;
m_conf.verts = m_vertex->buff;
m_conf.nverts = m_vertex->next;
m_conf.nindices = m_index->tail * 6;
m_conf.verts = m_vertex.buff;
m_conf.nverts = m_vertex.next;
m_conf.nindices = m_index.tail * 6;
m_conf.indices_per_prim = 6;
return;
}
@@ -5107,19 +5106,19 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
if (req_vert_backup)
{
memcpy(m_draw_vertex.buff, m_vertex->buff, sizeof(GSVertex) * m_vertex->next);
memcpy(m_draw_index.buff, m_index->buff, sizeof(u16) * m_index->tail);
memcpy(m_draw_vertex.buff, m_vertex.buff, sizeof(GSVertex) * m_vertex.next);
memcpy(m_draw_index.buff, m_index.buff, sizeof(u16) * m_index.tail);
m_conf.verts = m_draw_vertex.buff;
m_conf.indices = m_draw_index.buff;
}
else
{
m_conf.verts = m_vertex->buff;
m_conf.indices = m_index->buff;
m_conf.verts = m_vertex.buff;
m_conf.indices = m_index.buff;
}
m_conf.nverts = m_vertex->next;
m_conf.nindices = m_index->tail * 3;
m_conf.nverts = m_vertex.next;
m_conf.nindices = m_index.tail * 3;
m_conf.indices_per_prim = 6;
return;
}
@@ -5141,9 +5140,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
// See note above in GS_SPRITE_CLASS.
if (m_vt.m_accurate_stq && m_vt.m_eq.stq) [[unlikely]]
{
GSVertex* const v = m_vertex->buff;
GSVertex* const v = m_vertex.buff;
const GSVector4 v_q = GSVector4(v[0].RGBAQ.Q);
for (u32 i = 0; i < m_vertex->next; i++)
for (u32 i = 0; i < m_vertex.next; i++)
{
// v[i].ST.ST /= v[i].RGBAQ.Q; v[i].RGBAQ.Q = 1.0f; (Q / Q = 1)
GSVector4 v_st = GSVector4::load<true>(&v[i].ST);
@@ -5160,19 +5159,19 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
if (req_vert_backup)
{
memcpy(m_draw_vertex.buff, m_vertex->buff, sizeof(GSVertex) * m_vertex->next);
memcpy(m_draw_index.buff, m_index->buff, sizeof(u16) * m_index->tail);
memcpy(m_draw_vertex.buff, m_vertex.buff, sizeof(GSVertex) * m_vertex.next);
memcpy(m_draw_index.buff, m_index.buff, sizeof(u16) * m_index.tail);
m_conf.verts = m_draw_vertex.buff;
m_conf.indices = m_draw_index.buff;
}
else
{
m_conf.verts = m_vertex->buff;
m_conf.indices = m_index->buff;
m_conf.verts = m_vertex.buff;
m_conf.indices = m_index.buff;
}
m_conf.nverts = m_vertex->next;
m_conf.nindices = m_index->tail;
m_conf.nverts = m_vertex.next;
m_conf.nindices = m_index.tail;
}
void GSRendererHW::EmulateZbuffer(const GSTextureCache::Target* ds)
@@ -5196,7 +5195,9 @@ void GSRendererHW::EmulateZbuffer(const GSTextureCache::Target* ds)
m_conf.cb_vs.max_depth = GSVector2i(0xFFFFFFFF);
//ps_cb.MaxDepth = GSVector4(0.0f, 0.0f, 0.0f, 1.0f);
m_conf.ps.zclamp = 0;
m_conf.ps.zfloor = !m_cached_ctx.ZBUF.ZMSK;
m_conf.ps.zfloor = !m_vt.m_eq.z &&
(m_vt.m_primclass == GS_TRIANGLE_CLASS || m_vt.m_primclass == GS_LINE_CLASS) &&
(m_cached_ctx.DepthWrite() || (m_cached_ctx.DepthRead() && m_cached_ctx.TEST.ZTST == ZTST_GREATER));
if (clamp_z)
{
@@ -5231,7 +5232,7 @@ void GSRendererHW::EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GS
ConvertSpriteTextureShuffle(process_rg, process_ba, shuffle_across, rt, tex);
if (m_index->tail == 0)
if (m_index.tail == 0)
return; // Rewriting sprites can result in an empty draw.
// If date is enabled you need to test the green channel instead of the alpha channel.
@@ -5419,7 +5420,7 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
m_conf.ps.urban_chaos_hle = 1;
}
}
else if (m_index->tail <= 64 && !IsPageCopy() && m_cached_ctx.CLAMP.WMT == 3)
else if (m_index.tail <= 64 && !IsPageCopy() && m_cached_ctx.CLAMP.WMT == 3)
{
// Blood will tell. I think it is channel effect too but again
// implemented in a different way. I don't want to add more CRC stuff. So
@@ -5436,7 +5437,7 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
}
else if (m_cached_ctx.CLAMP.WMS == 3 && ((m_cached_ctx.CLAMP.MAXU & 0x8) == 8))
{
const ChannelFetch channel_select = ((m_cached_ctx.CLAMP.WMT != 3 && (m_vertex->buff[m_index->buff[0]].V & 0x20) == 0) || (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 0))) ? ChannelFetch_BLUE : ChannelFetch_ALPHA;
const ChannelFetch channel_select = ((m_cached_ctx.CLAMP.WMT != 3 && (m_vertex.buff[m_index.buff[0]].V & 0x20) == 0) || (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 0))) ? ChannelFetch_BLUE : ChannelFetch_ALPHA;
// MGS3/Kill Zone
if (test_only)
@@ -5450,7 +5451,7 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
{
// Read either Red or Green. Let's check the V coordinate. 0-1 is likely top so
// red. 2-3 is likely bottom so green (actually depends on texture base pointer offset)
const bool green = (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 2)) || (PRIM->FST && (m_vertex->buff[0].V & 32));
const bool green = (m_cached_ctx.CLAMP.WMT == 3 && ((m_cached_ctx.CLAMP.MAXV & 0x2) == 2)) || (PRIM->FST && (m_vertex.buff[0].V & 32));
if (green && (m_cached_ctx.FRAME.FBMSK & 0x00FFFFFF) == 0x00FFFFFF)
{
// Typically used in Terminator 3
@@ -5583,7 +5584,7 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
m_channel_shuffle_src_valid = src->m_valid;
if (GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled || ((src->m_TEX0.TBW == rt->m_TEX0.TBW) && (!m_in_target_draw && IsPageCopy())) || m_conf.ps.urban_chaos_hle || m_conf.ps.tales_of_abyss_hle)
{
GSVertex* s = &m_vertex->buff[0];
GSVertex* s = &m_vertex.buff[0];
s[0].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 0);
s[1].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + 16384);
s[0].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + 0);
@@ -5625,7 +5626,7 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
}
m_in_target_draw |= frame_page_offset > 0;
GSVertex* s = &m_vertex->buff[0];
GSVertex* s = &m_vertex.buff[0];
s[0].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + (m_r.x << 4));
s[1].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + (m_r.z << 4));
s[0].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + (m_r.y << 4));
@@ -5657,8 +5658,8 @@ __ri u32 GSRendererHW::EmulateChannelShuffle(GSTextureCache::Target* src, bool t
m_channel_shuffle_finish = true;
}
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
m_primitive_covers_without_gaps = NoGapsType::FullCover;
m_conf.cb_ps.ChannelShuffleOffset = GSVector2(0, 0);
@@ -5909,6 +5910,7 @@ void GSRendererHW::EmulateBlending(int rt_alpha_min, int rt_alpha_max, const boo
{
case AccBlendLevel::Maximum:
sw_blending |= true;
accumulation_blend &= (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;
@@ -6602,7 +6604,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
if (can_offset && tex->m_scale > 1.0f)
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
if (PRIM->FST)
{
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
@@ -6673,7 +6675,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
if (can_offset && tex->m_scale > 1.0f)
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
if (PRIM->FST)
{
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
@@ -6696,7 +6698,7 @@ __ri void GSRendererHW::EmulateTextureSampler(const GSTextureCache::Target* rt,
}
}
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index->tail >= 4 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index.tail >= 4 && GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].bpp >= 16 &&
((tex->m_from_target_TEX0.PSM & 0x30) == 0x30 || GSLocalMemory::m_psm[m_cached_ctx.TEX0.PSM].pal > 0))
{
HandleManualDeswizzle();
@@ -7382,7 +7384,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
if (rt)
{
EmulateTextureShuffleAndFbmask(rt, tex);
if (m_index->tail == 0)
if (m_index.tail == 0)
{
GL_INS("HW: DrawPrims: Texture shuffle emulation culled all vertices; exiting.");
return;
@@ -7580,7 +7582,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
GL_PERF("DATE: Fast with alpha %d-%d", GetAlphaMinMax().min, GetAlphaMinMax().max);
DATE_one = true;
}
else if (features.texture_barrier && ((m_vt.m_primclass == GS_SPRITE_CLASS && ComputeDrawlistGetSize(rt->m_scale) < 10) || (m_index->tail < 30)))
else if (features.texture_barrier && ((m_vt.m_primclass == GS_SPRITE_CLASS && ComputeDrawlistGetSize(rt->m_scale) < 10) || (m_index.tail < 30)))
{
// texture barrier will split the draw call into n draw call. It is very efficient for
// few primitive draws. Otherwise it sucks.
@@ -7995,7 +7997,7 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
// This kinda screws things up when using ST, so let's not.
if (m_vt.m_primclass == GS_SPRITE_CLASS && rtscale > 1.0f && (tex && PRIM->FST))
{
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const int x1_frac = ((v[1].XYZ.X - m_context->XYOFFSET.OFX) & 0xf);
const int y1_frac = ((v[1].XYZ.Y - m_context->XYOFFSET.OFY) & 0xf);
if (x1_frac & 8)
@@ -8549,7 +8551,7 @@ bool GSRendererHW::DetectStripedDoubleClear(bool& no_rt, bool& no_ds)
!m_cached_ctx.ZBUF.ZMSK &&
(m_cached_ctx.FRAME.PSM & 0x30) != (m_cached_ctx.ZBUF.PSM & 0x30) &&
(m_cached_ctx.FRAME.PSM & 0xF) == (m_cached_ctx.ZBUF.PSM & 0xF) && m_vt.m_eq.z == 1 &&
m_vertex->buff[1].XYZ.Z == m_vertex->buff[1].RGBAQ.U32[0];
m_vertex.buff[1].XYZ.Z == m_vertex.buff[1].RGBAQ.U32[0];
// Z and color must be constant and the same and must be drawing strips.
if (!z_is_frame || m_vt.m_eq.rgba != 0xFFFF)
@@ -8564,12 +8566,12 @@ bool GSRendererHW::DetectStripedDoubleClear(bool& no_rt, bool& no_ds)
// LOTR has 4096 verts, so this isn't going to be super fast on that game, most games will be just 16 verts so they should be ok,
// and I could cheat and stop when we get a size that matches, but that might be a lucky misdetection, I don't wanna risk it.
int vertex_offset = 0;
int last_vertex = m_vertex->buff[0].XYZ.X;
int last_vertex = m_vertex.buff[0].XYZ.X;
for (u32 i = 1; i < m_vertex->tail; i++)
for (u32 i = 1; i < m_vertex.tail; i++)
{
vertex_offset = std::max(static_cast<int>((m_vertex->buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
last_vertex = m_vertex->buff[i].XYZ.X;
vertex_offset = std::max(static_cast<int>((m_vertex.buff[i].XYZ.X - last_vertex) >> 4), vertex_offset);
last_vertex = m_vertex.buff[i].XYZ.X;
// Found a gap which is much bigger, no point continuing to scan.
if (vertex_offset > strip_size)
@@ -8929,7 +8931,7 @@ bool GSRendererHW::TryTargetClear(GSTextureCache::Target* rt, GSTextureCache::Ta
if (ds && !preserve_depth && m_r.rintersect(ds->m_valid).eq(ds->m_valid))
{
const u32 max_z = 0xFFFFFFFF >> (GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].fmt * 8);
const u32 z = std::min(max_z, m_vertex->buff[1].XYZ.Z);
const u32 z = std::min(max_z, m_vertex.buff[1].XYZ.Z);
const float d = static_cast<float>(z) * 0x1p-32f;
GL_INS("HW: TryTargetClear(): DS at %x <= %f", ds->m_TEX0.TBP0, d);
g_gs_device->ClearDepth(ds->m_texture, d);
@@ -8995,7 +8997,7 @@ bool GSRendererHW::TryGSMemClear(bool no_rt, bool preserve_rt, bool invalidate_r
if (!no_ds && !preserve_z)
{
ClearGSLocalMemory(m_context->offset.zb, m_r, m_vertex->buff[1].XYZ.Z);
ClearGSLocalMemory(m_context->offset.zb, m_r, m_vertex.buff[1].XYZ.Z);
if (invalidate_z)
{
@@ -9170,7 +9172,7 @@ void GSRendererHW::ClearGSLocalMemory(const GSOffset& off, const GSVector4i& r,
bool GSRendererHW::OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* tex, const GSVector4i& r_draw)
{
// Not required when using Tex in RT
if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex->next == 2) && m_process_texture && !PRIM->ABE &&
if (r_draw.w > 1024 && (m_vt.m_primclass == GS_SPRITE_CLASS) && (m_vertex.next == 2) && m_process_texture && !PRIM->ABE &&
tex && !tex->m_target && m_cached_ctx.TEX0.TBW > 0 && GSConfig.UserHacks_TextureInsideRt == GSTextureInRtMode::Disabled)
{
GL_PUSH("HW: OI_BlitFMV");
@@ -9285,12 +9287,12 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
}
// Simple case: one sprite.
if (m_index->tail == 2)
if (m_index.tail == 2)
{
return true;
}
const GSVertex* v = &m_vertex->buff[0];
const GSVertex* v = &m_vertex.buff[0];
const int first_dpY = v[1].XYZ.Y - v[0].XYZ.Y;
const int first_dpX = v[1].XYZ.X - v[0].XYZ.X;
const int first_dtV = v[1].V - v[0].V;
@@ -9300,7 +9302,7 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
if ((first_dpX >> 4) == m_r.z)
{
// Borrowed from MergeSprite() modified to calculate heights.
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < m_vertex.next; i += 2)
{
const int last_tV = v[i - 1].V;
const int dtV = v[i + 1].V - v[i].V;
@@ -9318,7 +9320,7 @@ bool GSRendererHW::TextureCoversWithoutGapsNotEqual()
if ((first_dpY >> 4) == m_r.w)
{
// Borrowed from MergeSprite().
for (u32 i = 2; i < m_vertex->next; i += 2)
for (u32 i = 2; i < m_vertex.next; i += 2)
{
const int last_tU = v[i - 1].U;
const int this_start_U = v[i].U;
@@ -9368,7 +9370,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
return 0;
const bool no_resize = (std::abs(draw_size.x - tex_size.x) <= 1 && std::abs(draw_size.y - tex_size.y) <= 1);
const bool can_maintain = no_resize || (!is_target_src && m_index->tail == 2);
const bool can_maintain = no_resize || (!is_target_src && m_index.tail == 2);
if (!src || ((!is_target_src || (src->m_from_target->m_downscaled || GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive)) && can_maintain))
return -1;
@@ -9404,9 +9406,9 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
}
// Last ditched check if it's doing a lot of small draws exactly the same which could be recursive lighting bloom.
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index->tail > 2 && !no_gaps_or_single_sprite && m_context->TEX1.MMAG == 1 && !m_context->ALPHA.IsOpaque())
if (m_vt.m_primclass == GS_SPRITE_CLASS && m_index.tail > 2 && !no_gaps_or_single_sprite && m_context->TEX1.MMAG == 1 && !m_context->ALPHA.IsOpaque())
{
GSVertex* v = &m_vertex->buff[0];
GSVertex* v = &m_vertex.buff[0];
float tw = 1 << src->m_TEX0.TW;
float th = 1 << src->m_TEX0.TH;
@@ -9417,7 +9419,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
if (first_x > first_u && first_y > first_v && !no_resize && std::abs(draw_size.x - first_x) <= 4 && std::abs(draw_size.y - first_y) <= 4)
{
for (u32 i = 2; i < m_index->tail; i += 2)
for (u32 i = 2; i < m_index.tail; i += 2)
{
const int next_u = (PRIM->FST) ? (v[i + 1].U - v[i].U) >> 4 : std::floor(static_cast<int>(tw * v[i + 1].ST.S) - static_cast<int>(tw * v[i].ST.S));
const int next_v = (PRIM->FST) ? (v[i + 1].V - v[i].V) >> 4 : std::floor(static_cast<int>(th * v[i + 1].ST.T) - static_cast<int>(th * v[i].ST.T));
@@ -9430,7 +9432,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
if (next_u != first_u || next_v != first_v || next_x != first_x || next_y != first_y)
break;
if (i + 2 >= m_index->tail)
if (i + 2 >= m_index.tail)
return 2;
}
}
@@ -9441,13 +9443,13 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps)
ClearType GSRendererHW::IsConstantDirectWriteMemClear()
{
const bool direct_draw = (m_vt.m_primclass == GS_SPRITE_CLASS) || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index->tail % 6) == 0 && TrianglesAreQuads());
const bool direct_draw = (m_vt.m_primclass == GS_SPRITE_CLASS) || (m_vt.m_primclass == GS_TRIANGLE_CLASS && (m_index.tail % 6) == 0 && TrianglesAreQuads());
// Constant Direct Write without texture/test/blending (aka a GS mem clear)
if (direct_draw && !PRIM->TME // Direct write
&& !(m_draw_env->SCANMSK.MSK & 2) && !m_cached_ctx.TEST.ATE // no alpha test
&& !m_cached_ctx.TEST.DATE // no destination alpha test
&& (!m_cached_ctx.TEST.ZTE || m_cached_ctx.TEST.ZTST == ZTST_ALWAYS) // no depth test
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex->next == 2) // constant color write
&& (m_vt.m_eq.rgba == 0xFFFF || m_vertex.next == 2) // constant color write
&& (!PRIM->FGE || m_vt.m_min.p.w == 255.0f)) // No fog effect
{
if ((PRIM->ABE && !m_context->ALPHA.IsOpaque()) || (m_cached_ctx.FRAME.FBMSK & GSLocalMemory::m_psm[m_cached_ctx.FRAME.PSM].fmsk))
@@ -9462,7 +9464,7 @@ u32 GSRendererHW::GetConstantDirectWriteMemClearColor() const
{
// Take the vertex colour, but check if the blending would make it black.
const u32 vert_index = (m_vt.m_primclass == GS_TRIANGLE_CLASS) ? 2 : 1;
u32 vert_color = m_vertex->buff[m_index->buff[vert_index]].RGBAQ.U32[0];
u32 vert_color = m_vertex.buff[m_index.buff[vert_index]].RGBAQ.U32[0];
if (PRIM->ABE && m_context->ALPHA.IsBlack())
vert_color &= 0xFF000000u;
@@ -9483,7 +9485,7 @@ u32 GSRendererHW::GetConstantDirectWriteMemClearColor() const
u32 GSRendererHW::GetConstantDirectWriteMemClearDepth() const
{
const u32 max_z = (0xFFFFFFFF >> (GSLocalMemory::m_psm[m_cached_ctx.ZBUF.PSM].fmt * 8));
return std::min(m_vertex->buff[1].XYZ.Z, max_z);
return std::min(m_vertex.buff[1].XYZ.Z, max_z);
}
bool GSRendererHW::IsReallyDithered() const
@@ -9505,7 +9507,7 @@ void GSRendererHW::ReplaceVerticesWithSprite(const GSVector4i& unscaled_rect, co
{
const GSVector4i fpr = unscaled_rect.sll32<4>();
const GSVector4i fpuv = unscaled_uv_rect.sll32<4>();
GSVertex* v = m_vertex->buff;
GSVertex* v = m_vertex.buff;
v[0].XYZ.X = static_cast<u16>(m_context->XYOFFSET.OFX + fpr.x);
v[0].XYZ.Y = static_cast<u16>(m_context->XYOFFSET.OFY + fpr.y);
@@ -9547,8 +9549,8 @@ void GSRendererHW::ReplaceVerticesWithSprite(const GSVector4i& unscaled_rect, co
m_vt.m_eq.z = true;
m_vt.m_eq.f = true;
m_vertex->head = m_vertex->tail = m_vertex->next = 2;
m_index->tail = 2;
m_vertex.head = m_vertex.tail = m_vertex.next = 2;
m_index.tail = 2;
m_r = unscaled_rect;
m_context->scissor.in = scissor;
@@ -9574,10 +9576,10 @@ void GSRendererHW::OffsetDraw(s32 fbp_offset, s32 zbp_offset, s32 xoffset, s32 y
const s32 fp_xoffset = xoffset << 4;
const s32 fp_yoffset = yoffset << 4;
for (u32 i = 0; i < m_vertex->next; i++)
for (u32 i = 0; i < m_vertex.next; i++)
{
m_vertex->buff[i].XYZ.X += fp_xoffset;
m_vertex->buff[i].XYZ.Y += fp_yoffset;
m_vertex.buff[i].XYZ.X += fp_xoffset;
m_vertex.buff[i].XYZ.Y += fp_yoffset;
}
}

View File

@@ -39,21 +39,21 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
GSRasterizerData data;
GSScanlineGlobalData& gd = data.global;
hw.m_sw_vertex_buffer.resize(((hw.m_vertex->next + 1) & ~1));
hw.m_sw_vertex_buffer.resize(((hw.m_vertex.next + 1) & ~1));
data.primclass = vt.m_primclass;
data.buff = nullptr;
data.vertex = hw.m_sw_vertex_buffer.data();
data.vertex_count = hw.m_vertex->next;
data.index = hw.m_index->buff;
data.index_count = hw.m_index->tail;
data.vertex_count = hw.m_vertex.next;
data.index = hw.m_index.buff;
data.index_count = hw.m_index.tail;
data.scanmsk_value = env.SCANMSK.MSK;
// Skip per pixel division if q is constant.
// Optimize the division by 1 with a nop. It also means that GS_SPRITE_CLASS must be processed when !vt.m_eq.q.
// If you have both GS_SPRITE_CLASS && vt.m_eq.q, it will depends on the first part of the 'OR'.
const u32 q_div = !hw.IsMipMapActive() && ((vt.m_eq.q && vt.m_min.t.z != 1.0f) || (!vt.m_eq.q && vt.m_primclass == GS_SPRITE_CLASS));
GSVertexSW::s_cvb[vt.m_primclass][PRIM->TME][PRIM->FST][q_div](context, data.vertex, hw.m_vertex->buff, hw.m_vertex->next);
GSVertexSW::s_cvb[vt.m_primclass][PRIM->TME][PRIM->FST][q_div](context, data.vertex, hw.m_vertex.buff, hw.m_vertex.next);
GSVector4i scissor = context->scissor.in;
GSVector4i bbox = GSVector4i(vt.m_min.p.floor().xyxy(vt.m_max.p.ceil())).rintersect(scissor);
@@ -524,12 +524,12 @@ bool GSRendererHWFunctions::SwPrimRender(GSRendererHW& hw, bool invalidate_tc, b
const u32 ofx = context->XYOFFSET.OFX;
for (int i = 0, j = hw.m_vertex->tail; i < j; i++)
for (int i = 0, j = hw.m_vertex.tail; i < j; i++)
{
#if _M_SSE >= 0x501
if ((((hw.m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
if ((((hw.m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
#else
if ((((hw.m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
if ((((hw.m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
#endif
{
gd.sel.notest = 0;

View File

@@ -2404,250 +2404,289 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
};
Target* dst = nullptr;
auto& list = m_dst[type];
Target* dst_match = nullptr;
auto* list = &m_dst[type];
const GSVector4i min_rect = draw_rect.max_u32(GSVector4i(0, 0, draw_rect.x, draw_rect.y));
// TODO: Move all frame stuff to its own routine too.
if (!is_frame)
{
for (auto i = list.begin(); i != list.end();)
for (int iteration = 0; iteration < 2; iteration++)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0)
{
bool can_use = true;
if (dst != nullptr)
break;
if (dst && ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw) && dst->m_TEX0.TBP0 <= bp))
auto& new_dst = iteration == 0 ? dst : dst_match;
list = &m_dst[iteration == 0 ? type : (1 - type)];
for (auto i = list->begin(); i != list->end();)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0)
{
DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, dst->m_TEX0.TBP0);
i++;
continue;
}
// if It's an old target and it's being completely overwritten, kill it.
// Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But,
// it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing
// targets at BW=1 because it breaks other games, we can when the *new* buffer area is completely dirty.
if (((!preserve_rgb && !preserve_alpha) || (t->m_was_dst_matched && fbmask == 0xffffff)) && TEX0.TBW != t->m_TEX0.TBW)
{
// Old targets or shrunk targets where Y draw height goes outside the page.
if (TEX0.TBW > 1 && (t->m_age >= 1 || (type == RenderTarget && draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && TEX0.TBW < t->m_TEX0.TBW)))
bool can_use = true;
if (new_dst && ((GSState::s_n - new_dst->m_last_draw) < (GSState::s_n - t->m_last_draw) && new_dst->m_TEX0.TBP0 <= bp))
{
DevCon.Warning("Ignoring target at %x as one at %x is newer", t->m_TEX0.TBP0, new_dst->m_TEX0.TBP0);
i++;
continue;
}
// If there's no valid RGB, it can't be interchanged between RT and Depth
if (iteration == 1)
{
const u32 valid_mask = (t->m_valid_rgb ? 0x7 : 0x0) | ((t->m_valid_alpha_low || t->m_valid_alpha_high) ? 0x8 : 0x0);
if ((!(valid_mask & GSUtil::GetChannelMask(TEX0.PSM)) || (!is_shuffle && TEX0.TBW < (t->m_TEX0.TBW / 2))) ||
(!is_shuffle && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp != GSLocalMemory::m_psm[TEX0.PSM].bpp))
{
if (!preserve_rgb && !preserve_alpha && (!src || src->m_from_target != t) && (valid_mask & GSUtil::GetChannelMask(TEX0.PSM)))
{
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
continue;
}
}
// if It's an old target and it's being completely overwritten, kill it.
// Dragon Quest 8 reuses a render-target sized buffer as a single-page buffer, without clearing it. But,
// it does dirty it by writing over the 64x64 region. So while we can't use this heuristic for tossing
// targets at BW=1 because it breaks other games, we can when the *new* buffer area is completely dirty.
if (((!preserve_rgb && !preserve_alpha) || (t->m_was_dst_matched && fbmask == 0xffffff)) && TEX0.TBW != t->m_TEX0.TBW)
{
// Old targets or shrunk targets where Y draw height goes outside the page.
if (TEX0.TBW > 1 && (t->m_age >= 1 || (type == RenderTarget && draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && TEX0.TBW < t->m_TEX0.TBW)))
{
can_use = false;
}
else if (!t->m_dirty.empty())
{
const GSVector4i size_rect = GSVector4i::loadh(size);
can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect);
}
}
else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW))
{
// When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts).
auto& rev_list = m_dst[1 - type];
for (auto j = rev_list.begin(); j != rev_list.end(); ++j)
{
Target* ds = *j;
if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW)
continue;
t->m_was_dst_matched = true;
t->m_valid_rgb = false;
break;
}
}
// TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets:
// 1. They can draw wider than the FBW
// 2. The dirty+valid rects will need to also be rearranged
// 3. This could mean larger targets hanging around more
// 4. Sources which reference a target may become invalid and will need to be removed
// 5. Potential performance implications from additional render passes/copying
//
// But the bonuses are:
// 1. Rearranging the page layout will fix quite a few games which do this
// 2. Preserved data will be in the correct place (in most cases)
// 3. Less deleting sources/targets
// 4. We can basically do clears in hardware, if they aren't insane ones
bool dirtied_area = t->m_dirty.size() >= 1;
// Check it covers the whole area of the new draw
if (!is_shuffle && dirtied_area)
{
const u32 draw_start = GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const u32 draw_end = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const u32 dirty_start = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
const u32 dirty_end = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
if (dirty_end < draw_end || dirty_start > draw_start)
dirtied_area = false;
}
if (can_use && ((!is_shuffle && dirtied_area) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16)) && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW)
{
can_use = false;
}
else if (!t->m_dirty.empty())
if (can_use)
{
const GSVector4i size_rect = GSVector4i::loadh(size);
can_use = !t->m_dirty.GetTotalRect(TEX0, size).rintersect(size_rect).eq(size_rect);
}
}
else if (type == RenderTarget && (fbmask == 0xffffff && !t->m_was_dst_matched && TEX0.TBW != t->m_TEX0.TBW))
{
// When returning to being matched with the Z buffer in width, we need to make sure the RGB is up to date as it could get used later (Hitman Contracts).
auto& rev_list = m_dst[1 - type];
for (auto j = rev_list.begin(); j != rev_list.end(); ++j)
{
Target* ds = *j;
if (t->m_TEX0.TBP0 != ds->m_TEX0.TBP0 || !ds->m_valid_rgb || TEX0.TBW != ds->m_TEX0.TBW)
continue;
t->m_was_dst_matched = true;
t->m_valid_rgb = false;
break;
}
}
// TODO: What might be a nicer solution than this, is to rearrange the targets to match the new layout, however this comes with some caviets:
// 1. They can draw wider than the FBW
// 2. The dirty+valid rects will need to also be rearranged
// 3. This could mean larger targets hanging around more
// 4. Sources which reference a target may become invalid and will need to be removed
// 5. Potential performance implications from additional render passes/copying
//
// But the bonuses are:
// 1. Rearranging the page layout will fix quite a few games which do this
// 2. Preserved data will be in the correct place (in most cases)
// 3. Less deleting sources/targets
// 4. We can basically do clears in hardware, if they aren't insane ones
bool dirtied_area = t->m_dirty.size() >= 1;
// Check it covers the whole area of the new draw
if (!is_shuffle && dirtied_area)
{
const u32 draw_start = GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const u32 draw_end = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
const GSVector4i dirty_rect = t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const u32 dirty_start = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
const u32 dirty_end = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, dirty_rect);
if (dirty_end < draw_end || dirty_start > draw_start)
dirtied_area = false;
}
if (can_use && ((!is_shuffle && dirtied_area) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp == 16)) && ((preserve_alpha && preserve_rgb) || (draw_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y && !possible_clear)) && TEX0.TBW != t->m_TEX0.TBW)
{
can_use = false;
}
if (can_use)
{
if (used)
list.MoveFront(i.Index());
dst = t;
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
else if (!(src && src->m_from_target == t))
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
}
// Probably pointing to half way through the target
else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets)
{
// Some games misuse the scissor so it ends up valid 1 pixel over, which causes hell for us. So check if it still overlaps without the extra pixel.
const GSVector4i adjusted_valid = GSVector4i(t->m_valid.x, t->m_valid.y, std::min(t->m_valid.z, static_cast<int>(t->m_TEX0.TBW) * 64), t->m_valid.w - 1);
const u32 adjusted_endblock = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, adjusted_valid);
if (adjusted_endblock <= bp)
{
i++;
continue;
}
const u32 widthpage_offset = (std::abs(static_cast<int>(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U);
const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast<int>((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0);
const bool no_target_or_newer = (!dst || ((GSState::s_n - dst->m_last_draw) < (GSState::s_n - t->m_last_draw)));
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
const bool ds_offset = !ds || offset != 0;
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
const bool source_match = src && src->m_TEX0.TBP0 <= bp && src->m_end_block > bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect);
const bool was_used_last_draw = t->m_last_draw == (GSState::s_n - 1);
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32)));
if (source_match || (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer || was_used_last_draw)))
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
// If it overlaps but the target is huge and the Z isn't offset, we need to split the buffer, so let's shrink this one down.
// 896 is just 448 * 2,just gives the buffer chance to be larger than normal, in case they do something like 640x640, or something ridiculous.
if (!is_shuffle && (ds && offset == 0 && (t->m_valid.w >= 896) && ((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) <= bp))
{
const u32 local_offset = (((bp - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * s_psm.pgs.y;
if ((dst = CreateTarget(TEX0, GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), scale, type, true, fbmask, false, false, preserve_rgb || preserve_alpha, GSVector4i::zero(), src)))
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
// I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target.
if (is_shuffle && src && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) ||
((widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)))
{
const int page_offset = TEX0.TBP0 - t->m_TEX0.TBP0;
const int number_pages = page_offset / 32;
const u32 tbw = std::max(t->m_TEX0.TBW, 1u);
const int row_offset = number_pages / tbw;
const int page_height = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int vertical_position = row_offset * page_height;
if (src && src->m_from_target == t && src->m_target_direct && vertical_position >= t->m_valid.w / 2)
{
// Valids and drawn since last read doesn't match, keep the target but resize it.
src->m_valid_rect.w = std::min(vertical_position, src->m_valid_rect.w);
t->m_valid.w = std::min(vertical_position, t->m_valid.w);
t->ResizeValidity(t->m_valid);
t->ResizeDrawn(t->m_valid);
++i;
}
else
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
}
continue;
}
GSVector4i lookup_rect = min_rect;
if (is_shuffle)
lookup_rect = lookup_rect & GSVector4i(~8);
const GSVector4i translated_rect = GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, lookup_rect));
const GSVector4i dirty_rect = t->m_dirty.empty() ? GSVector4i::zero() : t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const bool all_dirty = dirty_rect.eq(t->m_valid);
if (!is_shuffle && !dirty_rect.rempty() && (!preserve_alpha && !preserve_rgb) && (GSState::s_n - 3) > t->m_last_draw)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to dirty areas not preserved (Likely change in target)", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list.erase(i);
delete t;
continue;
}
if (!all_dirty && ((translated_rect.w <= t->m_valid.w) || widthpage_offset == 0 || (GSState::s_n - 3) <= t->m_last_draw))
{
if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63) / 64) > 1)
{
// Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080
// which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080.
// So let's check the *real* start.
u32 real_start_address = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_drawn_since_read);
u32 new_end_address = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect);
// Not really overlapping.
if (real_start_address > new_end_address)
{
i++;
continue;
}
}
//DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width());
dst = t;
dst->m_32_bits_fmt |= (psm_s.bpp != 16);
//Continue just in case there's a newer target
if (used)
list.MoveFront(i.Index());
if (t->m_TEX0.TBP0 <= bp || GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect) >= bp)
break;
else
continue;
list->MoveFront(i.Index());
new_dst = t;
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
else if (!(src && src->m_from_target == t))
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
continue;
}
}
}
// Probably pointing to half way through the target
else if (!min_rect.rempty() && GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets)
{
// Some games misuse the scissor so it ends up valid 1 pixel over, which causes hell for us. So check if it still overlaps without the extra pixel.
const GSVector4i adjusted_valid = GSVector4i(t->m_valid.x, t->m_valid.y, std::min(t->m_valid.z, static_cast<int>(t->m_TEX0.TBW) * 64), t->m_valid.w - 1);
const u32 adjusted_endblock = GSLocalMemory::GetEndBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, adjusted_valid);
if (adjusted_endblock <= bp)
{
i++;
continue;
}
i++;
const u32 widthpage_offset = (std::abs(static_cast<int>(bp - t->m_TEX0.TBP0)) >> 5) % std::max(t->m_TEX0.TBW, 1U);
const bool is_aligned_ok = widthpage_offset == 0 || ((min_rect.width() <= static_cast<int>((t->m_TEX0.TBW - widthpage_offset) * 64) && (t->m_TEX0.TBW == TEX0.TBW || TEX0.TBW == 1)) && bp >= t->m_TEX0.TBP0);
const bool no_target_or_newer = (!new_dst || ((GSState::s_n - new_dst->m_last_draw) < (GSState::s_n - t->m_last_draw)));
const bool width_match = (t->m_TEX0.TBW == TEX0.TBW || (TEX0.TBW == 1 && draw_rect.w <= GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y));
const bool ds_offset = !ds || offset != 0;
const bool is_double_buffer = TEX0.TBP0 == ((((t->m_end_block + 1) - t->m_TEX0.TBP0) / 2) + t->m_TEX0.TBP0);
const bool source_match = src && src->m_TEX0.TBP0 <= bp && src->m_end_block > bp && src->m_TEX0.TBW == TEX0.TBW && src->m_from_target && src->m_from_target == t && t->Inside(bp, TEX0.TBW, TEX0.PSM, min_rect);
const bool was_used_last_draw = t->m_last_draw == (GSState::s_n - 1);
// if it's a shuffle, some games tend to offset back by a page, such as Tomb Raider, for no disernable reason, but it then causes problems.
// This can also happen horizontally (Catwoman moves everything one page left with shuffles), but this is too messy to deal with right now.
const bool overlaps = t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect) || (is_shuffle && src && GSLocalMemory::m_psm[src->m_TEX0.PSM].bpp == 8 && t->Overlaps(bp, TEX0.TBW, TEX0.PSM, min_rect + GSVector4i(0, 0, 0, 32)));
if (source_match || (no_target_or_newer && is_aligned_ok && width_match && overlaps && (is_shuffle || ds_offset || is_double_buffer || was_used_last_draw)))
{
const GSLocalMemory::psm_t& s_psm = GSLocalMemory::m_psm[TEX0.PSM];
// If it overlaps but the target is huge and the Z isn't offset, we need to split the buffer, so let's shrink this one down.
// 896 is just 448 * 2,just gives the buffer chance to be larger than normal, in case they do something like 640x640, or something ridiculous.
if (!is_shuffle && (ds && offset == 0 && (t->m_valid.w >= 896) && ((((t->m_end_block + 1) - t->m_TEX0.TBP0) >> 1) + t->m_TEX0.TBP0) <= bp))
{
const u32 local_offset = (((bp - t->m_TEX0.TBP0) >> 5) / std::max(t->m_TEX0.TBW, 1U)) * s_psm.pgs.y;
if ((new_dst = CreateTarget(TEX0, GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), GSVector2i(t->m_valid.z, t->m_valid.w - local_offset), scale, type, true, fbmask, false, false, preserve_rgb || preserve_alpha, GSVector4i::zero(), src)))
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
break;
}
// I know what you're thinking, and I hate the guy who wrote it too (me). Project Snowblind, Tomb Raider etc decide to offset where they're drawing using a channel shuffle, and this gets messy, so best just to kill the old target.
if (is_shuffle && src && src->m_TEX0.PSM == PSMT8 && GSRendererHW::GetInstance()->m_context->FRAME.FBW == 1 && t->m_last_draw != (GSState::s_n - 1) && src->m_from_target && (src->m_from_target->m_TEX0.TBP0 == src->m_TEX0.TBP0 || (((src->m_TEX0.TBP0 - src->m_from_target->m_TEX0.TBP0) >> 5) % std::max(src->m_from_target->m_TEX0.TBW, 1U) == 0)) && widthpage_offset && src->m_from_target != t)
{
if (iteration == 0)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s offset overwrite shuffle", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
continue;
}
if (!is_shuffle && (!GSUtil::HasSameSwizzleBits(t->m_TEX0.PSM, TEX0.PSM) ||
((widthpage_offset % std::max(t->m_TEX0.TBW, 1U)) != 0 && ((widthpage_offset + (min_rect.width() + (s_psm.pgs.x - 1)) / s_psm.pgs.x)) > t->m_TEX0.TBW)))
{
const int page_offset = TEX0.TBP0 - t->m_TEX0.TBP0;
const int number_pages = page_offset / 32;
const u32 tbw = std::max(t->m_TEX0.TBW, 1u);
const int row_offset = number_pages / tbw;
const int page_height = GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y;
const int vertical_position = row_offset * page_height;
if (src && src->m_from_target == t && src->m_target_direct && vertical_position >= t->m_valid.w / 2)
{
// Valids and drawn since last read doesn't match, keep the target but resize it.
src->m_valid_rect.w = std::min(vertical_position, src->m_valid_rect.w);
t->m_valid.w = std::min(vertical_position, t->m_valid.w);
t->ResizeValidity(t->m_valid);
t->ResizeDrawn(t->m_valid);
++i;
}
else
{
if (iteration == 0)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to change in target", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
}
else
i++;
}
continue;
}
GSVector4i lookup_rect = min_rect;
if (is_shuffle)
lookup_rect = lookup_rect & GSVector4i(~8);
const GSVector4i translated_rect = GSVector4i(0, 0, 0, 0).max_i32(TranslateAlignedRectByPage(t, TEX0.TBP0, TEX0.PSM, TEX0.TBW, lookup_rect));
const GSVector4i dirty_rect = t->m_dirty.empty() ? GSVector4i::zero() : t->m_dirty.GetTotalRect(t->m_TEX0, t->m_unscaled_size);
const bool all_dirty = dirty_rect.eq(t->m_valid);
if (!is_shuffle && !dirty_rect.rempty() && (!preserve_alpha && !preserve_rgb) && (GSState::s_n - 3) > t->m_last_draw)
{
GL_INS("TC: Deleting RT BP 0x%x BW %d PSM %s due to dirty areas not preserved (Likely change in target)", t->m_TEX0.TBP0, t->m_TEX0.TBW, GSUtil::GetPSMName(t->m_TEX0.PSM));
InvalidateSourcesFromTarget(t);
i = list->erase(i);
delete t;
continue;
}
if (!all_dirty && ((translated_rect.w <= t->m_valid.w) || widthpage_offset == 0 || (GSState::s_n - 3) <= t->m_last_draw))
{
if (TEX0.TBW == t->m_TEX0.TBW && !is_shuffle && widthpage_offset == 0 && ((min_rect.w + 63) / 64) > 1)
{
// Beyond Good and Evil does this awful thing where it puts one framebuffer at 0xf00, with the first row of pages blanked out, and the whole thing goes down to 0x2080
// which is a problem, because it then puts the Z buffer at 0x1fc0, then offsets THAT by 1 row of pages, so it starts at, you guessed it, 2080.
// So let's check the *real* start.
u32 real_start_address = GSLocalMemory::GetStartBlockAddress(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, t->m_drawn_since_read);
u32 new_end_address = GSLocalMemory::GetEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect);
// Not really overlapping.
if (real_start_address > new_end_address)
{
i++;
continue;
}
}
//DevCon.Warning("Here draw %d wanted %x PSM %x got %x PSM %x offset of %d pages width %d pages draw width %d", GSState::s_n, bp, TEX0.PSM, t->m_TEX0.TBP0, t->m_TEX0.PSM, (bp - t->m_TEX0.TBP0) >> 5, t->m_TEX0.TBW, draw_rect.width());
new_dst = t;
new_dst->m_32_bits_fmt |= (psm_s.bpp != 16);
//Continue just in case there's a newer target
if (used)
list->MoveFront(i.Index());
if (t->m_TEX0.TBP0 <= bp || GSLocalMemory::GetStartBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, min_rect) >= bp)
break;
else
continue;
}
}
}
i++;
}
}
}
else
{
pxAssert(type == RenderTarget);
// Let's try to find a perfect frame that contains valid data
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
@@ -2673,7 +2712,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -2691,7 +2730,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// 2nd try ! Try to find a frame at the requested bp -> bp + size is inside of (or equal to)
if (!dst)
{
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
const u32 end_block = GSLocalMemory::GetEndBlockAddress(bp, TEX0.TBW, TEX0.PSM, GSVector4i(0, size.y, size.x, size.y + 1));
@@ -2711,7 +2750,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("2 Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -2731,7 +2770,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// 3rd try ! Try to find a frame that doesn't contain valid data (honestly I'm not sure we need to do it)
if (!dst)
{
for (auto i = list.begin(); i != list.end(); ++i)
for (auto i = list->begin(); i != list->end(); ++i)
{
Target* t = *i;
if (bp == t->m_TEX0.TBP0 && TEX0.TBW == t->m_TEX0.TBW)
@@ -2743,7 +2782,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
{
DevCon.Warning("3 Wanted %x psm %x bw %x, got %x psm %x bw %x, deleting", TEX0.TBP0, TEX0.PSM, TEX0.TBW, t->m_TEX0.TBP0, t->m_TEX0.PSM, t->m_TEX0.TBW);
InvalidateSourcesFromTarget(t);
i = list.erase(i);
i = list->erase(i);
delete t;
continue;
}
@@ -3088,67 +3127,28 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
}
else if (!is_frame && !GSConfig.UserHacks_DisableDepthSupport)
{
const int rev_type = (type == DepthStencil) ? RenderTarget : DepthStencil;
// Depth stencil/RT can be an older RT/DS but only check recent RT/DS to avoid to pick
// some bad data.
auto& rev_list = m_dst[rev_type];
Target* dst_match = nullptr;
for (auto i = rev_list.begin(); i != rev_list.end(); ++i)
if (!dst_match)
{
Target* t = *i;
// Don't pull in targets without valid lower 24 bits unless the Z is 32bits and the alpha is valid, it makes no sense to convert them otherwise.
// FIXME: Technically the difference in size is fine, but if the target gets reinterpreted, the hw renderer doesn't rearrange the target.
// This does cause some extra uploads in some games (like Burnout), but without this, bad data gets displayed in games like Transformers.
if (bp != t->m_TEX0.TBP0 || (!t->m_valid_rgb && (!(GSUtil::GetChannelMask(TEX0.PSM) & 0x8) || !(t->m_valid_alpha_low || t->m_valid_alpha_high))) ||
(!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && (possible_clear || ((~GSLocalMemory::m_psm[t->m_TEX0.PSM].fmsk | fbmask) == 0xffffffff))))
const int rev_type = (type == DepthStencil) ? RenderTarget : DepthStencil;
// Depth stencil/RT can be an older RT/DS but only check recent RT/DS to avoid to pick
// some bad data.
auto& rev_list = m_dst[rev_type];
for (auto i = rev_list.begin(); i != rev_list.end(); ++i)
{
continue;
}
// If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid.
// Make sure it's not currently in use, that could be bad.
if (!is_shuffle && (!ds || (ds != t)) &&
t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)
{
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
Target* t = *i;
// Don't pull in targets without valid lower 24 bits unless the Z is 32bits and the alpha is valid, it makes no sense to convert them otherwise.
// FIXME: Technically the difference in size is fine, but if the target gets reinterpreted, the hw renderer doesn't rearrange the target.
// This does cause some extra uploads in some games (like Burnout), but without this, bad data gets displayed in games like Transformers.
if (bp != t->m_TEX0.TBP0 || (!t->m_valid_rgb && (!(GSUtil::GetChannelMask(TEX0.PSM) & 0x8) || !(t->m_valid_alpha_low || t->m_valid_alpha_high))) ||
(!is_shuffle && t->m_TEX0.TBW != TEX0.TBW && (possible_clear || ((~GSLocalMemory::m_psm[t->m_TEX0.PSM].fmsk | fbmask) == 0xffffffff))))
{
src->m_target_direct = false;
src->m_shared_texture = false;
t->m_texture = nullptr;
continue;
}
GL_CACHE("TC: Deleting Z draw %d", GSState::s_n);
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM];
if (t_psm_s.bpp != psm_s.bpp)
{
bool remove_target = possible_clear || (used && !is_shuffle);
// If we have a BW change, and it's not a multiple of 2 (for a shuffle), the game's going to get a jigsaw
// puzzle of pages and can't be expecting to have legitimate data. Tokimeki Memorial 3 reuses a BW 17
// buffer as BW 10, and if we don't discard the BW 17 buffer, the BW 10 buffer ends up twice the size.
const u32 shuffle_bw = (psm_s.bpp > t_psm_s.bpp) ? (TEX0.TBW / 2u) : (TEX0.TBW * 2u);
if (t->m_TEX0.TBW != TEX0.TBW && (t->m_TEX0.TBW != shuffle_bw && !is_shuffle))
// If the format is completely different, but it's the same location, it's likely just overwriting it, so get rid.
// Make sure it's not currently in use, that could be bad.
if (!is_shuffle && (!ds || (ds != t)) &&
t->m_TEX0.TBW != TEX0.TBW && TEX0.TBW != 1 && !preserve_rgb && min_rect.w > GSLocalMemory::m_psm[t->m_TEX0.PSM].pgs.y)
{
// But we'll make sure the whole existing target's actually being drawn over to be safe.
const u32 end_block = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
if (end_block >= t->UnwrappedEndBlock())
{
GL_CACHE("TC: Not converting %s at %x TBW %u with end block of %x when we're drawing through %x",
to_string(rev_type), t->m_TEX0.TBP0, t->m_TEX0.TBW, t->UnwrappedEndBlock(), end_block);
remove_target = true;
}
}
// Probably an old target, get rid of it.
if (remove_target)
{
// DT Racer hits this path and causes a crash when RT in RT is disabled,
// so let's make sure source and target texture isn't linked/shared before deleting the target.
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
{
src->m_target_direct = false;
@@ -3156,26 +3156,68 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
t->m_texture = nullptr;
}
GL_CACHE("TC: Deleting Z draw %d", GSState::s_n);
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
}
const GSLocalMemory::psm_t& t_psm_s = GSLocalMemory::m_psm[t->m_TEX0.PSM];
if (t_psm_s.bpp != psm_s.bpp)
{
bool remove_target = possible_clear || (used && !is_shuffle);
if (t->m_age == 0)
{
dst_match = t;
break;
}
else if (t->m_age == 1 && (preserve_rgb || (preserve_alpha && (t->m_valid_alpha_low || t->m_valid_alpha_high))))
{
dst_match = t;
// If we have a BW change, and it's not a multiple of 2 (for a shuffle), the game's going to get a jigsaw
// puzzle of pages and can't be expecting to have legitimate data. Tokimeki Memorial 3 reuses a BW 17
// buffer as BW 10, and if we don't discard the BW 17 buffer, the BW 10 buffer ends up twice the size.
const u32 shuffle_bw = (psm_s.bpp > t_psm_s.bpp) ? (TEX0.TBW / 2u) : (TEX0.TBW * 2u);
if (t->m_TEX0.TBW != TEX0.TBW && (t->m_TEX0.TBW != shuffle_bw && !is_shuffle))
{
// But we'll make sure the whole existing target's actually being drawn over to be safe.
const u32 end_block = GSLocalMemory::GetUnwrappedEndBlockAddress(TEX0.TBP0, TEX0.TBW, TEX0.PSM, draw_rect);
if (end_block >= t->UnwrappedEndBlock())
{
GL_CACHE("TC: Not converting %s at %x TBW %u with end block of %x when we're drawing through %x",
to_string(rev_type), t->m_TEX0.TBP0, t->m_TEX0.TBW, t->UnwrappedEndBlock(), end_block);
remove_target = true;
}
}
// Probably an old target, get rid of it.
if (remove_target)
{
// DT Racer hits this path and causes a crash when RT in RT is disabled,
// so let's make sure source and target texture isn't linked/shared before deleting the target.
if (src && src->m_target && src->m_from_target == t && src->m_target_direct)
{
src->m_target_direct = false;
src->m_shared_texture = false;
t->m_texture = nullptr;
}
InvalidateSourcesFromTarget(t);
i = rev_list.erase(i);
delete t;
continue;
}
}
if (t->m_age == 0)
{
dst_match = t;
break;
}
else if (t->m_age == 1 && (preserve_rgb || (preserve_alpha && (t->m_valid_alpha_low || t->m_valid_alpha_high))))
{
dst_match = t;
}
}
}
// We only want to use a matched target if it's actually being used.
if (dst_match)
{
// dst_match, we only want to use a matched target if it's actually being used.
calcRescale(dst_match);
// If we don't need A, and the existing target doesn't have valid alpha, don't bother converting it.
@@ -3192,7 +3234,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
// Clear instead of invalidating if there is anything which isn't touched.
clear |= (!preserve_target && fbmask != 0);
GIFRegTEX0 new_TEX0;
new_TEX0.TBP0 = TEX0.TBP0;
new_TEX0.TBP0 = GSConfig.UserHacks_TextureInsideRt >= GSTextureInRtMode::InsideTargets ? dst_match->m_TEX0.TBP0 : TEX0.TBP0;
new_TEX0.TBW = (!half_width) ? dst_match->m_TEX0.TBW : TEX0.TBW;
new_TEX0.PSM = is_shuffle ? dst_match->m_TEX0.PSM : TEX0.PSM;
@@ -3207,8 +3249,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe
dst->m_valid_alpha_low = dst_match->m_valid_alpha_low; //&& psm_s.trbpp != 24;
dst->m_valid_alpha_high = dst_match->m_valid_alpha_high; //&& psm_s.trbpp != 24;
dst->m_valid_rgb = dst_match->m_valid_rgb && (dst->m_TEX0.TBW == TEX0.TBW || min_rect.w <= GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].pgs.y);
dst->m_was_dst_matched = true;
dst_match->m_was_dst_matched = true;
dst->m_was_dst_matched = !dst_match->m_was_dst_matched;
dst_match->m_valid_rgb = preserve_rgb;
if (GSLocalMemory::m_psm[dst->m_TEX0.PSM].bpp == 16 && GSLocalMemory::m_psm[dst_match->m_TEX0.PSM].bpp > 16)
@@ -4377,6 +4418,7 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
t->m_valid_alpha_low &= preserve_alpha;
t->m_valid_alpha_high &= preserve_alpha;
t->m_valid_rgb &= (fb_mask & 0x00FFFFFF) != 0;
t->m_was_dst_matched = false;
// Don't keep partial depth buffers around.
if ((!t->m_valid_alpha_low && !t->m_valid_alpha_high && !t->m_valid_rgb) || type == DepthStencil)
@@ -4387,7 +4429,22 @@ void GSTextureCache::InvalidateContainedTargets(u32 start_bp, u32 end_bp, u32 wr
Target* const rev_t = *j;
if (rev_t->m_TEX0.TBP0 == t->m_TEX0.TBP0 && GSLocalMemory::m_psm[rev_t->m_TEX0.PSM].bpp == GSLocalMemory::m_psm[t->m_TEX0.PSM].bpp)
{
rev_t->m_was_dst_matched = false;
if (t->m_last_draw > rev_t->m_last_draw && GSUtil::GetChannelMask(t->m_TEX0.PSM) == GSUtil::GetChannelMask(rev_t->m_TEX0.PSM))
{
if (type == DepthStencil && GSUtil::GetChannelMask(t->m_TEX0.PSM) == 0x7)
{
rev_t->m_valid_rgb = false;
rev_t->m_was_dst_matched = false;
}
else
{
GL_CACHE("TC: InvalidateContainedTargets: Remove Target %s[%x, %s]", to_string(1 - type), rev_t->m_TEX0.TBP0, GSUtil::GetPSMName(rev_t->m_TEX0.PSM));
rev_list.erase(j);
delete rev_t;
}
}
else
rev_t->m_was_dst_matched = false;
break;
}
++j;
@@ -5091,14 +5148,14 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u
// We use dx/dy == 0 and the TBW check as a safeguard to make sure these go through to local memory.
// We can also recreate the target if it's previously been created in the height cache with a valid size.
// Good test case for this is the Xenosaga I cutscene transitions, or Gradius V.
if (src && !dst && ((dx == 0 && dy == 0 && ((static_cast<u32>(w) + 63) / 64) <= DBW) || HasTargetInHeightCache(DBP, DBW, DPSM, 10)))
if (src && !dst && ((((dx == 0 && dy == 0) || (dx == sx && dy == sy && DBW == src->m_TEX0.TBW)) && ((static_cast<u32>(w) + 63) / 64) <= DBW) || HasTargetInHeightCache(DBP, DBW, DPSM, 10)))
{
GIFRegTEX0 new_TEX0 = {};
new_TEX0.TBP0 = DBP;
new_TEX0.TBW = DBW;
new_TEX0.PSM = DPSM;
const GSVector2i target_size = GetTargetSize(DBP, DBW, DPSM, Common::AlignUpPow2(w, 64), h);
const GSVector2i target_size = (dx == 0 && dy == 0) ? GetTargetSize(DBP, DBW, DPSM, Common::AlignUpPow2(w, 64), h) : GSVector2i(src->m_valid.z, src->m_valid.w);
dst = LookupTarget(new_TEX0, target_size, src->m_scale, src->m_type);
if (!dst)
{

View File

@@ -770,6 +770,11 @@ bool GSDeviceOGL::CheckFeatures()
m_features.line_expand ? "hardware" : (m_features.vs_expand ? "vertex expanding" : "UNSUPPORTED"),
m_features.vs_expand ? "vertex expanding" : "CPU");
if (!GLAD_GL_ARB_conservative_depth)
{
Console.Warning("GLAD_GL_ARB_conservative_depth is not supported. This will reduce performance.");
}
return true;
}
@@ -1293,6 +1298,16 @@ std::string GSDeviceOGL::GenGlslHeader(const std::string_view entry, GLenum type
else
header += "#define HAS_FRAMEBUFFER_FETCH 0\n";
if (GLAD_GL_ARB_conservative_depth)
{
header += "#extension GL_ARB_conservative_depth : enable\n";
header += "#define HAS_CONSERVATIVE_DEPTH 1\n";
}
else
{
header += "#define HAS_CONSERVATIVE_DEPTH 0\n";
}
// Allow to puts several shader in 1 files
switch (type)
{

View File

@@ -320,14 +320,14 @@ void GSRendererSW::RewriteVerticesIfSTOverflow()
constexpr int n = GSUtil::GetClassVertexCount(primclass);
// Make sure the copy buffer is large enough.
while (m_vertex->maxcount < m_index->tail)
while (m_vertex.maxcount < m_index.tail)
GrowVertexBuffer();
GSVertex* RESTRICT vertex = m_vertex->buff;
GSVertex* RESTRICT vertex_copy = m_vertex->buff_copy;
u16* RESTRICT index = m_index->buff;
GSVertex* RESTRICT vertex = m_vertex.buff;
GSVertex* RESTRICT vertex_copy = m_vertex.buff_copy;
u16* RESTRICT index = m_index.buff;
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
for (int i = 0; i < static_cast<int>(m_index.tail); i += n)
{
GSVector4 stcq[n];
@@ -381,18 +381,18 @@ void GSRendererSW::RewriteVerticesIfSTOverflow()
}
// Swap the buffers and fix the counts.
std::swap(m_vertex->buff, m_vertex->buff_copy);
m_vertex->head = m_vertex->next = m_vertex->tail = m_index->tail;
std::swap(m_vertex.buff, m_vertex.buff_copy);
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;
// Recalculate ST min/max/eq in the vertex trace.
GSVector4 tmin = GSVector4::cxpr(FLT_MAX);
GSVector4 tmax = GSVector4::cxpr(-FLT_MAX);
for (int i = 0; i < static_cast<int>(m_index->tail); i += n)
for (int i = 0; i < static_cast<int>(m_index.tail); i += n)
{
for (int j = 0; j < n; j++)
{
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex->buff[i + j].m[0]));
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex->buff[i + 1].RGBAQ.Q;
GSVector4 stcq = GSVector4::cast(GSVector4i(m_vertex.buff[i + j].m[0]));
const float Q = (primclass == GS_SPRITE_CLASS) ? stcq.w : m_vertex.buff[i + 1].RGBAQ.Q;
stcq = (stcq / Q).xyzw(stcq);
tmin = tmin.min(stcq);
@@ -451,11 +451,11 @@ void GSRendererSW::Draw()
SharedData* sd = static_cast<SharedData*>(data.get());
sd->primclass = m_vt.m_primclass;
sd->buff = (u8*)m_vertex_heap.alloc(sizeof(GSVertexSW) * ((m_vertex->next + 1) & ~1) + sizeof(u32) * m_index->tail, 64);
sd->buff = (u8*)m_vertex_heap.alloc(sizeof(GSVertexSW) * ((m_vertex.next + 1) & ~1) + sizeof(u32) * m_index.tail, 64);
sd->vertex = (GSVertexSW*)sd->buff;
sd->vertex_count = m_vertex->next;
sd->index = (u16*)(sd->buff + sizeof(GSVertexSW) * ((m_vertex->next + 1) & ~1));
sd->index_count = m_index->tail;
sd->vertex_count = m_vertex.next;
sd->index = (u16*)(sd->buff + sizeof(GSVertexSW) * ((m_vertex.next + 1) & ~1));
sd->index_count = m_index.tail;
sd->scanmsk_value = m_draw_env->SCANMSK.MSK;
// skip per pixel division if q is constant.
@@ -463,9 +463,9 @@ void GSRendererSW::Draw()
// If you have both GS_SPRITE_CLASS && m_vt.m_eq.q, it will depends on the first part of the 'OR'
u32 q_div = !IsMipMapActive() && ((m_vt.m_eq.q && m_vt.m_min.t.z != 1.0f) || (!m_vt.m_eq.q && m_vt.m_primclass == GS_SPRITE_CLASS));
GSVertexSW::s_cvb[m_vt.m_primclass][PRIM->TME][PRIM->FST][q_div](m_context, sd->vertex, m_vertex->buff, m_vertex->next);
GSVertexSW::s_cvb[m_vt.m_primclass][PRIM->TME][PRIM->FST][q_div](m_context, sd->vertex, m_vertex.buff, m_vertex.next);
std::memcpy(sd->index, m_index->buff, sizeof(u16) * m_index->tail);
std::memcpy(sd->index, m_index.buff, sizeof(u16) * m_index.tail);
GSVector4i scissor = context->scissor.in;
GSVector4i bbox = GSVector4i(m_vt.m_min.p.floor().upld(m_vt.m_max.p.floor())) + GSVector4i(0, 0, 1, 1); // right/bottom should be exclusive so +1
@@ -485,12 +485,12 @@ void GSRendererSW::Draw()
{
int n = GSUtil::GetVertexCount(PRIM->PRIM);
for (u32 i = 0, j = 0; i < m_index->tail; i += n, j++)
for (u32 i = 0, j = 0; i < m_index.tail; i += n, j++)
{
for (int k = 0; k < n; k++)
{
GSVertex* v = &m_vertex->buff[m_index->buff[i + k]];
GSVertex* vn = &m_vertex->buff[m_index->buff[i + n - 1]];
GSVertex* v = &m_vertex.buff[m_index.buff[i + k]];
GSVertex* vn = &m_vertex.buff[m_index.buff[i + n - 1]];
fprintf(s_fp, "%d:%d %f %f %f %f\n",
j, k,
@@ -1505,12 +1505,12 @@ bool GSRendererSW::GetScanlineGlobalData(SharedData* data)
u32 ofx = context->XYOFFSET.OFX;
for (int i = 0, j = m_vertex->tail; i < j; i++)
for (int i = 0, j = m_vertex.tail; i < j; i++)
{
#if _M_SSE >= 0x501
if ((((m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
if ((((m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 7) // aligned to 8
#else
if ((((m_vertex->buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
if ((((m_vertex.buff[i].XYZ.X - ofx) + 15) >> 4) & 3) // aligned to 4
#endif
{
gd.sel.notest = 0;

View File

@@ -3131,7 +3131,7 @@ void GSDeviceVK::UpdateCLUTTexture(
GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, GSTexture* dTex, u32 dOffset, u32 dSize)
{
// Super annoying, but apparently NVIDIA doesn't like floats/ints packed together in the same vec4?
struct Uniforms
struct alignas(16) Uniforms
{
u32 offsetX, offsetY, dOffset, pad1;
float scale;
@@ -3150,7 +3150,7 @@ void GSDeviceVK::UpdateCLUTTexture(
void GSDeviceVK::ConvertToIndexedTexture(
GSTexture* sTex, float sScale, u32 offsetX, u32 offsetY, u32 SBW, u32 SPSM, GSTexture* dTex, u32 DBW, u32 DPSM)
{
struct Uniforms
struct alignas(16) Uniforms
{
u32 SBW;
u32 DBW;
@@ -3171,7 +3171,7 @@ void GSDeviceVK::ConvertToIndexedTexture(
void GSDeviceVK::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32 downsample_factor, const GSVector2i& clamp_min, const GSVector4& dRect)
{
struct Uniforms
struct alignas(16) Uniforms
{
GSVector2i clamp_min;
int downsample_factor;
@@ -4434,16 +4434,13 @@ void GSDeviceVK::RenderImGui()
UpdateImGuiTextures();
const float uniforms[2][2] = {{
2.0f / static_cast<float>(m_window_info.surface_width),
2.0f / static_cast<float>(m_window_info.surface_height),
},
{
-1.0f,
-1.0f,
}};
const GSVector4 uniforms(
2.0f / static_cast<float>(m_window_info.surface_width),
2.0f / static_cast<float>(m_window_info.surface_height),
-1.0f,
-1.0f);
SetUtilityPushConstants(uniforms, sizeof(uniforms));
SetUtilityPushConstants(&uniforms, sizeof(uniforms));
SetPipeline(m_imgui_pipeline);
if (m_utility_sampler != m_linear_sampler)

View File

@@ -380,7 +380,6 @@ static const char* s_gs_hw_fix_names[] = {
"trilinearFiltering",
"skipDrawStart",
"skipDrawEnd",
"halfBottomOverride",
"halfPixelOffset",
"roundSprite",
"nativeScaling",

View File

@@ -65,7 +65,6 @@ namespace GameDatabaseSchema
TrilinearFiltering,
SkipDrawStart,
SkipDrawEnd,
HalfBottomOverride,
HalfPixelOffset,
RoundSprite,
NativeScaling,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,496 @@
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "FullscreenUI.h"
#include "ImGuiFullscreen.h"
#include "common/Timer.h"
#include "Input/InputManager.h"
#define TR_CONTEXT "FullscreenUI"
template <size_t L>
class IconStackString : public SmallStackString<L>
{
public:
__fi IconStackString(const char* icon, const char* str)
{
SmallStackString<L>::format("{} {}", icon, Host::TranslateToStringView(TR_CONTEXT, str));
}
__fi IconStackString(const char8_t* icon, const char* str)
{
SmallStackString<L>::format("{} {}", reinterpret_cast<const char*>(icon), Host::TranslateToStringView(TR_CONTEXT, str));
}
__fi IconStackString(const char* icon, const char* str, const char* suffix)
{
SmallStackString<L>::format("{} {}##{}", icon, Host::TranslateToStringView(TR_CONTEXT, str), suffix);
}
__fi IconStackString(const char8_t* icon, const char* str, const char* suffix)
{
SmallStackString<L>::format("{} {}##{}", reinterpret_cast<const char*>(icon), Host::TranslateToStringView(TR_CONTEXT, str), suffix);
}
};
#define FSUI_ICONSTR(icon, str) IconStackString<256>(icon, str).c_str()
#define FSUI_ICONSTR_S(icon, str, suffix) IconStackString<256>(icon, str, suffix).c_str()
#define FSUI_STR(str) Host::TranslateToString(TR_CONTEXT, str)
#define FSUI_CSTR(str) Host::TranslateToCString(TR_CONTEXT, str)
#define FSUI_VSTR(str) Host::TranslateToStringView(TR_CONTEXT, str)
#define FSUI_FSTR(str) fmt::runtime(Host::TranslateToStringView(TR_CONTEXT, str))
#define FSUI_NSTR(str) str
using ImGuiFullscreen::ActiveButton;
using ImGuiFullscreen::AddNotification;
using ImGuiFullscreen::BeginFullscreenColumns;
using ImGuiFullscreen::BeginFullscreenColumnWindow;
using ImGuiFullscreen::BeginFullscreenWindow;
using ImGuiFullscreen::BeginHorizontalMenu;
using ImGuiFullscreen::BeginMenuButtons;
using ImGuiFullscreen::BeginNavBar;
using ImGuiFullscreen::CenterImage;
using ImGuiFullscreen::CloseChoiceDialog;
using ImGuiFullscreen::CloseFileSelector;
using ImGuiFullscreen::EndFullscreenColumns;
using ImGuiFullscreen::EndFullscreenColumnWindow;
using ImGuiFullscreen::EndFullscreenWindow;
using ImGuiFullscreen::EndHorizontalMenu;
using ImGuiFullscreen::EndMenuButtons;
using ImGuiFullscreen::EndNavBar;
using ImGuiFullscreen::EnumChoiceButton;
using ImGuiFullscreen::FloatingButton;
using ImGuiFullscreen::FocusResetType;
using ImGuiFullscreen::ForceKeyNavEnabled;
using ImGuiFullscreen::g_large_font;
using ImGuiFullscreen::g_layout_padding_left;
using ImGuiFullscreen::g_layout_padding_top;
using ImGuiFullscreen::g_medium_font;
using ImGuiFullscreen::GetCachedSvgTexture;
using ImGuiFullscreen::GetCachedSvgTextureAsync;
using ImGuiFullscreen::GetCachedTexture;
using ImGuiFullscreen::GetCachedTextureAsync;
using ImGuiFullscreen::GetPlaceholderTexture;
using ImGuiFullscreen::GetQueuedFocusResetType;
using ImGuiFullscreen::HorizontalMenuItem;
using ImGuiFullscreen::HorizontalMenuSvgItem;
using ImGuiFullscreen::InputFilterType;
using ImGuiFullscreen::IsFocusResetQueued;
using ImGuiFullscreen::IsGamepadInputSource;
using ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT;
using ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE;
using ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE;
using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT;
using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY;
using ImGuiFullscreen::LAYOUT_MENU_BUTTON_X_PADDING;
using ImGuiFullscreen::LAYOUT_MENU_BUTTON_Y_PADDING;
using ImGuiFullscreen::LAYOUT_SCREEN_HEIGHT;
using ImGuiFullscreen::LAYOUT_SCREEN_WIDTH;
using ImGuiFullscreen::LayoutScale;
using ImGuiFullscreen::LoadSvgTexture;
using ImGuiFullscreen::LoadTexture;
using ImGuiFullscreen::MenuButton;
using ImGuiFullscreen::MenuButtonFrame;
using ImGuiFullscreen::MenuButtonWithoutSummary;
using ImGuiFullscreen::MenuButtonWithValue;
using ImGuiFullscreen::MenuHeading;
using ImGuiFullscreen::MenuHeadingButton;
using ImGuiFullscreen::MenuImageButton;
using ImGuiFullscreen::ModAlpha;
using ImGuiFullscreen::MulAlpha;
using ImGuiFullscreen::NavButton;
using ImGuiFullscreen::NavTitle;
using ImGuiFullscreen::OpenChoiceDialog;
using ImGuiFullscreen::OpenConfirmMessageDialog;
using ImGuiFullscreen::OpenFileSelector;
using ImGuiFullscreen::OpenInfoMessageDialog;
using ImGuiFullscreen::OpenInputStringDialog;
using ImGuiFullscreen::PopPrimaryColor;
using ImGuiFullscreen::PushPrimaryColor;
using ImGuiFullscreen::QueueResetFocus;
using ImGuiFullscreen::ResetFocusHere;
using ImGuiFullscreen::RightAlignNavButtons;
using ImGuiFullscreen::SetFullscreenFooterText;
using ImGuiFullscreen::ShowToast;
using ImGuiFullscreen::SvgScaling;
using ImGuiFullscreen::ThreeWayToggleButton;
using ImGuiFullscreen::ToggleButton;
using ImGuiFullscreen::UIBackgroundColor;
using ImGuiFullscreen::UIBackgroundHighlightColor;
using ImGuiFullscreen::UIBackgroundLineColor;
using ImGuiFullscreen::UIBackgroundTextColor;
using ImGuiFullscreen::UIDisabledColor;
using ImGuiFullscreen::UIPopupBackgroundColor;
using ImGuiFullscreen::UIPrimaryColor;
using ImGuiFullscreen::UIPrimaryDarkColor;
using ImGuiFullscreen::UIPrimaryLightColor;
using ImGuiFullscreen::UIPrimaryLineColor;
using ImGuiFullscreen::UIPrimaryTextColor;
using ImGuiFullscreen::UISecondaryColor;
using ImGuiFullscreen::UISecondaryStrongColor;
using ImGuiFullscreen::UISecondaryTextColor;
using ImGuiFullscreen::UISecondaryWeakColor;
using ImGuiFullscreen::UITextHighlightColor;
using ImGuiFullscreen::WantsToCloseMenu;
namespace FullscreenUI
{
enum class MainWindowType
{
None,
Landing,
StartGame,
Exit,
GameList,
GameListSettings,
Settings,
PauseMenu,
Achievements,
Leaderboards,
};
enum class PauseSubMenu
{
None,
Exit,
Achievements,
};
enum class SettingsPage
{
Summary,
Interface,
BIOS,
Emulation,
Graphics,
Audio,
MemoryCard,
NetworkHDD,
Folders,
Achievements,
Controller,
Hotkey,
Advanced,
Patches,
Cheats,
GameFixes,
Count
};
enum class GameListView
{
Grid,
List,
Count
};
enum class IPAddressType
{
PS2IP,
SubnetMask,
Gateway,
DNS1,
DNS2,
Other
};
//////////////////////////////////////////////////////////////////////////
// Main
//////////////////////////////////////////////////////////////////////////
void UpdateGameDetails(std::string path, std::string serial, std::string title, u32 disc_crc, u32 crc);
bool AreAnyDialogsOpen();
void PauseForMenuOpen(bool set_pause_menu_open);
void ClosePauseMenu();
void OpenPauseSubMenu(PauseSubMenu submenu);
void DrawLandingTemplate(ImVec2* menu_pos, ImVec2* menu_size);
void DrawLandingWindow();
void DrawStartGameWindow();
void DrawExitWindow();
void DrawPauseMenu(MainWindowType type);
void ExitFullscreenAndOpenURL(const std::string_view url);
void CopyTextToClipboard(std::string title, const std::string_view text);
void DrawAboutWindow();
void OpenAboutWindow();
void GetStandardSelectionFooterText(SmallStringBase& dest, bool back_instead_of_cancel);
void ApplyLayoutSettings(const SettingsInterface* bsi = nullptr);
void DrawSvgTexture(GSTexture* padded_texture, ImVec2 unpadded_size);
void DrawCachedSvgTexture(const std::string& path, ImVec2 size, SvgScaling mode);
void DrawCachedSvgTextureAsync(const std::string& path, ImVec2 size, SvgScaling mode);
void DrawListSvgTexture(ImDrawList* drawList, GSTexture* padded_texture, const ImVec2& p_min, const ImVec2& p_unpadded_max);
inline MainWindowType s_current_main_window = MainWindowType::None;
inline PauseSubMenu s_current_pause_submenu = PauseSubMenu::None;
inline bool s_initialized = false;
inline bool s_tried_to_initialize = false;
inline bool s_pause_menu_was_open = false;
inline bool s_was_paused_on_quick_menu_open = false;
inline bool s_about_window_open = false;
// achievements login dialog state
inline bool s_achievements_login_open = false;
inline bool s_achievements_login_logging_in = 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;
// local copies of the currently-running game
inline std::string s_current_game_title;
inline std::string s_current_game_subtitle;
inline std::string s_current_disc_serial;
inline std::string s_current_disc_path;
inline u32 s_current_disc_crc;
//////////////////////////////////////////////////////////////////////////
// Resources
//////////////////////////////////////////////////////////////////////////
bool LoadResources();
bool LoadSvgResources();
void DestroyResources();
inline std::array<std::shared_ptr<GSTexture>, static_cast<u32>(GameDatabaseSchema::Compatibility::Perfect)>
s_game_compatibility_textures;
inline std::shared_ptr<GSTexture> s_banner_texture;
inline std::vector<std::unique_ptr<GSTexture>> s_cleanup_textures;
//////////////////////////////////////////////////////////////////////////
// Landing
//////////////////////////////////////////////////////////////////////////
void SwitchToLanding();
ImGuiFullscreen::FileSelectorFilters GetOpenFileFilters();
ImGuiFullscreen::FileSelectorFilters GetDiscImageFilters();
ImGuiFullscreen::FileSelectorFilters GetAudioFileFilters();
ImGuiFullscreen::FileSelectorFilters GetImageFileFilters();
void DoVMInitialize(const VMBootParameters& boot_params, bool switch_to_landing_on_failure);
void DoStartPath(
const std::string& path, std::optional<s32> state_index = std::nullopt, std::optional<bool> fast_boot = std::nullopt);
void DoStartFile();
void DoStartBIOS();
void DoStartDisc(const std::string& drive);
void DoStartDisc();
void DoToggleFrameLimit();
void DoToggleSoftwareRenderer();
void RequestShutdown(bool save_state);
void DoShutdown(bool save_state);
void RequestReset();
void DoReset();
void DoChangeDiscFromFile();
void RequestChangeDisc();
void DoRequestExit();
void DoDesktopMode();
void DoToggleFullscreen();
void ConfirmShutdownIfMemcardBusy(std::function<void(bool)> callback);
bool ShouldDefaultToGameList();
//////////////////////////////////////////////////////////////////////////
// Save State List
//////////////////////////////////////////////////////////////////////////
struct SaveStateListEntry
{
std::string title;
std::string summary;
std::string path;
std::unique_ptr<GSTexture> preview_texture;
time_t timestamp;
s32 slot;
};
void InitializePlaceholderSaveStateListEntry(SaveStateListEntry* li, s32 slot);
bool InitializeSaveStateListEntry(
SaveStateListEntry* li, const std::string& title, const std::string& serial, u32 crc, s32 slot, bool backup = false);
void ClearSaveStateEntryList();
u32 PopulateSaveStateListEntries(const std::string& title, const std::string& serial, u32 crc);
bool OpenLoadStateSelectorForGame(const std::string& game_path);
bool OpenSaveStateSelector(bool is_loading);
void CloseSaveStateSelector();
void DrawSaveStateSelector(bool is_loading);
bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry);
void DrawResumeStateSelector();
void DoLoadState(std::string path, std::optional<s32> slot, bool backup);
void DoSaveState(s32 slot);
inline std::vector<SaveStateListEntry> s_save_state_selector_slots;
inline std::string s_save_state_selector_game_path;
inline s32 s_save_state_selector_submenu_index = -1;
inline bool s_save_state_selector_open = false;
inline bool s_save_state_selector_loading = true;
inline bool s_save_state_selector_resuming = false;
//////////////////////////////////////////////////////////////////////////
// Game List
//////////////////////////////////////////////////////////////////////////
void DrawGameListWindow();
void DrawGameList(const ImVec2& heading_size);
void DrawGameGrid(const ImVec2& heading_size);
void HandleGameListActivate(const GameList::Entry* entry);
void HandleGameListOptions(const GameList::Entry* entry);
void DrawGameListSettingsWindow();
void SwitchToGameList();
void PopulateGameListEntryList();
GSTexture* GetTextureForGameListEntryType(GameList::EntryType type, const ImVec2& size, SvgScaling mode = SvgScaling::Stretch);
GSTexture* GetGameListCover(const GameList::Entry* entry);
void DrawGameCover(const GameList::Entry* entry, const ImVec2& size);
void DrawGameCover(const GameList::Entry* entry, ImDrawList* draw_list, const ImVec2& min, const ImVec2& max);
// For when we have no GameList entry
void DrawFallbackCover(const ImVec2& size);
void DrawFallbackCover(ImDrawList* draw_list, const ImVec2& min, const ImVec2& max);
// Lazily populated cover images.
inline std::unordered_map<std::string, std::string> s_cover_image_map;
inline std::vector<const GameList::Entry*> s_game_list_sorted_entries;
inline GameListView s_game_list_view = GameListView::Grid;
//////////////////////////////////////////////////////////////////////////
// Background
//////////////////////////////////////////////////////////////////////////
void LoadCustomBackground();
void DrawCustomBackground();
inline std::shared_ptr<GSTexture> s_custom_background_texture;
inline std::string s_custom_background_path;
inline bool s_custom_background_enabled = false;
//////////////////////////////////////////////////////////////////////////
// Achievements
//////////////////////////////////////////////////////////////////////////
void SwitchToAchievementsWindow();
void SwitchToLeaderboardsWindow();
void DrawAchievementsLoginWindow();
//////////////////////////////////////////////////////////////////////////
// Settings
//////////////////////////////////////////////////////////////////////////
static constexpr double INPUT_BINDING_TIMEOUT_SECONDS = 5.0;
static constexpr u32 NUM_MEMORY_CARD_PORTS = 2;
void SwitchToSettings();
void SwitchToGameSettings();
void SwitchToGameSettings(const std::string& path);
void SwitchToGameSettings(const GameList::Entry* entry);
void SwitchToGameSettings(const std::string_view serial, u32 crc);
void DrawSettingsWindow();
void DrawSummarySettingsPage();
void DrawInterfaceSettingsPage();
void DrawBIOSSettingsPage();
void DrawEmulationSettingsPage();
void DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_advanced_settings);
void DrawAudioSettingsPage();
void DrawMemoryCardSettingsPage();
void DrawNetworkHDDSettingsPage();
void DrawFoldersSettingsPage();
void DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& settings_lock);
void DrawControllerSettingsPage();
void DrawHotkeySettingsPage();
void DrawAdvancedSettingsPage();
void DrawPatchesOrCheatsSettingsPage(bool cheats);
void DrawGameFixesSettingsPage();
bool IsEditingGameSettings(SettingsInterface* bsi);
SettingsInterface* GetEditingSettingsInterface();
SettingsInterface* GetEditingSettingsInterface(bool game_settings);
bool ShouldShowAdvancedSettings(SettingsInterface* bsi);
void SetSettingsChanged(SettingsInterface* bsi);
bool GetEffectiveBoolSetting(SettingsInterface* bsi, const char* section, const char* key, bool default_value);
s32 GetEffectiveIntSetting(SettingsInterface* bsi, const char* section, const char* key, s32 default_value);
void DoCopyGameSettings();
void DoClearGameSettings();
void ResetControllerSettings();
void DoLoadInputProfile();
void DoSaveInputProfile();
void DoSaveInputProfile(const std::string& name);
void DoResetSettings();
bool DrawToggleSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
bool default_value, bool enabled = true, bool allow_tristate = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIntListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
int default_value, const char* const* options, size_t option_count, bool translate_options, int option_offset = 0,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIntRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
int default_value, int min_value, int max_value, const char* format = "%d", bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
int default_value, int min_value, int max_value, int step_value, const char* format = "%d", bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawFloatRangeSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
float default_value, float min_value, float max_value, const char* format = "%f", float multiplier = 1.0f, bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawFloatSpinBoxSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, float default_value, float min_value, float max_value, float step_value, float multiplier,
const char* format = "%f", bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIntRectSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* left_key, int default_left, const char* top_key, int default_top, const char* right_key, int default_right,
const char* bottom_key, int default_bottom, int min_value, int max_value, int step_value, const char* format = "%d",
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, const char* const* options, const char* const* option_values, size_t option_count,
bool translate_options, bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font, const char* translation_ctx = "FullscreenUI");
void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, SettingInfo::GetOptionsCallback options_callback, bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawIPAddressSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
const char* default_value, bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font,
IPAddressType ip_type = IPAddressType::Other);
void DrawFloatListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section, const char* key,
float default_value, const char* const* options, const float* option_values, size_t option_count, bool translate_options,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font);
template <typename DataType, typename SizeType>
void DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, DataType default_value,
std::optional<DataType> (*from_string_function)(const char* str),
const char* (*to_string_function)(DataType value),
const char* (*to_display_string_function)(DataType value), SizeType option_count,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
std::pair<ImFont*, float> font = g_large_font, std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawFolderSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key,
const std::string& runtime_var, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawPathSetting(SettingsInterface* bsi, const char* title, const char* section, const char* key, const char* default_value,
bool enabled = true, float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, std::pair<ImFont*, float> font = g_large_font,
std::pair<ImFont*, float> summary_font = g_medium_font);
void DrawClampingModeSetting(SettingsInterface* bsi, const char* title, const char* summary, int vunum);
void PopulateGraphicsAdapterList();
void PopulateGameListDirectoryCache(SettingsInterface* si);
void PopulatePatchesAndCheatsList(const std::string_view serial, u32 crc);
void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const std::string_view section,
const std::string_view key, const std::string_view display_name);
void DrawInputBindingWindow();
void DrawInputBindingButton(SettingsInterface* bsi, InputBindingInfo::Type type, const char* section, const char* name, const char* display_name, const char* icon_name, bool show_type = true);
void ClearInputBindingVariables();
void StartAutomaticBinding(u32 port);
void DrawSettingInfoSetting(SettingsInterface* bsi, const char* section, const char* key, const SettingInfo& si,
const char* translation_ctx);
void OpenMemoryCardCreateDialog();
void DoCreateMemoryCard(std::string name, MemoryCardType type, MemoryCardFileType file_type, bool use_ntfs_compression = false);
inline SettingsPage s_settings_page = SettingsPage::Interface;
inline std::unique_ptr<INISettingsInterface> s_game_settings_interface;
inline std::unique_ptr<GameList::Entry> s_game_settings_entry;
inline std::vector<std::pair<std::string, bool>> s_game_list_directories_cache;
inline std::vector<GSAdapterInfo> s_graphics_adapter_list_cache;
inline std::vector<Patch::PatchInfo> s_game_patch_list;
inline std::vector<std::string> s_enabled_game_patch_cache;
inline std::vector<Patch::PatchInfo> s_game_cheats_list;
inline std::vector<std::string> s_enabled_game_cheat_cache;
inline u32 s_game_cheat_unlabelled_count = 0;
inline std::vector<const HotkeyInfo*> s_hotkey_list_cache;
inline std::atomic_bool s_settings_changed{false};
inline std::atomic_bool s_game_settings_changed{false};
inline InputBindingInfo::Type s_input_binding_type = InputBindingInfo::Type::Unknown;
inline std::string s_input_binding_section;
inline std::string s_input_binding_key;
inline std::string s_input_binding_display_name;
inline std::vector<InputBindingKey> s_input_binding_new_bindings;
inline std::vector<std::pair<InputBindingKey, std::pair<float, float>>> s_input_binding_value_ranges;
inline Common::Timer s_input_binding_timer;
} // namespace FullscreenUI

File diff suppressed because it is too large Load Diff

View File

@@ -200,7 +200,6 @@ void SPU2::DoFullDump()
fprintf(dump, " - Sound Start Address: %x\n", Cores[c].Voices[v].StartA);
fprintf(dump, " - Next Data Address: %x\n", Cores[c].Voices[v].NextA);
fprintf(dump, " - Play Status: %s\n", (Cores[c].Voices[v].ADSR.Phase > 0) ? "Playing" : "Not Playing");
fprintf(dump, " - Block Sample: %d\n", Cores[c].Voices[v].SCurrent);
}
fprintf(dump, "#### END OF DUMP.\n\n");
}

View File

@@ -163,7 +163,7 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
if ((AutoDMACtrl & (Index + 1)) == 0)
{
ActiveTSA = 0x2000 + (Index << 10);
DMAICounter = size * 4;
DMAICounter = size * 48;
LastClock = psxRegs.cycle;
}
else if (size >= 256)
@@ -191,7 +191,7 @@ void V_Core::StartADMAWrite(u16* pMem, u32 sz)
if (SPU2::MsgToConsole())
SPU2::ConLog("ADMA%c Error Size of %x too small\n", GetDmaIndexChar(), size);
InputDataLeft = 0;
DMAICounter = size * 4;
DMAICounter = size * 48;
LastClock = psxRegs.cycle;
}
}
@@ -248,7 +248,7 @@ void V_Core::FinishDMAwrite()
DMA7LogWrite(DMAPtr, ReadSize << 1);
#endif
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 48));
u32 buff2end = 0;
if (buff1end > 0x100000)
{
@@ -343,7 +343,7 @@ void V_Core::FinishDMAwrite()
DMAPtr += TDA - ActiveTSA;
ReadSize -= TDA - ActiveTSA;
DMAICounter = (DMAICounter - ReadSize) * 4;
DMAICounter = (DMAICounter - ReadSize) * 48;
CounterUpdate(DMAICounter);
@@ -354,7 +354,7 @@ void V_Core::FinishDMAwrite()
void V_Core::FinishDMAread()
{
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 4));
u32 buff1end = ActiveTSA + std::min(ReadSize, (u32)0x100 + std::abs(DMAICounter / 48));
u32 buff2end = 0;
if (buff1end > 0x100000)
@@ -426,9 +426,9 @@ void V_Core::FinishDMAread()
// DMA Reads are done AFTER the delay, so to get the timing right we need to scheule one last DMA to catch IRQ's
if (ReadSize)
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
DMAICounter = std::min(ReadSize, (u32)0x100) * 48;
else
DMAICounter = 4;
DMAICounter = 48;
CounterUpdate(DMAICounter);
@@ -446,7 +446,7 @@ void V_Core::DoDMAread(u16* pMem, u32 size)
ReadSize = size;
IsDMARead = true;
LastClock = psxRegs.cycle;
DMAICounter = std::min(ReadSize, (u32)0x100) * 4;
DMAICounter = (std::min(ReadSize, (u32)0x100) * 48);
Regs.STATX &= ~0x80;
Regs.STATX |= 0x400;
//Regs.ATTR |= 0x30;
@@ -470,7 +470,7 @@ void V_Core::DoDMAwrite(u16* pMem, u32 size)
{
Regs.STATX &= ~0x80;
//Regs.ATTR |= 0x30;
DMAICounter = 1 * 4;
DMAICounter = 1 * 48;
LastClock = psxRegs.cycle;
return;
}

View File

@@ -89,55 +89,13 @@ int g_counter_cache_ignores = 0;
#define XAFLAG_LOOP (1ul << 1)
#define XAFLAG_LOOP_START (1ul << 2)
static __forceinline s32 GetNextDataBuffered(V_Core& thiscore, uint voiceidx)
static __forceinline void GetNextDataBuffered(V_Core& thiscore, uint voiceidx)
{
V_Voice& vc(thiscore.Voices[voiceidx]);
if ((vc.SCurrent & 3) == 0)
if (vc.SBuffer == nullptr)
{
IncrementNextA(thiscore, voiceidx);
if ((vc.NextA & 7) == 0) // vc.SCurrent == 24 equivalent
{
if (vc.LoopFlags & XAFLAG_LOOP_END)
{
thiscore.Regs.ENDX |= (1 << voiceidx);
vc.NextA = vc.LoopStartA | 1;
if (!(vc.LoopFlags & XAFLAG_LOOP))
{
vc.Stop();
if (IsDevBuild)
{
if (SPU2::MsgVoiceOff())
SPU2::ConLog("* SPU2: Voice Off by EndPoint: %d \n", voiceidx);
}
}
}
else
vc.NextA++; // no, don't IncrementNextA here. We haven't read the header yet.
}
}
if (vc.SCurrent == 28)
{
vc.SCurrent = 0;
// We'll need the loop flags and buffer pointers regardless of cache status:
for (int i = 0; i < 2; i++)
if (Cores[i].IRQEnable && Cores[i].IRQA == (vc.NextA & 0xFFFF8))
SetIrqCall(i);
s16* memptr = GetMemPtr(vc.NextA & 0xFFFF8);
vc.LoopFlags = *memptr >> 8; // grab loop flags from the upper byte.
if ((vc.LoopFlags & XAFLAG_LOOP_START) && !vc.LoopMode)
{
vc.LoopStartA = vc.NextA & 0xFFFF8;
}
const int cacheIdx = vc.NextA / pcm_WordsPerBlock;
const int cacheIdx = (vc.NextA & 0xFFFF8) / pcm_WordsPerBlock;
PcmCacheEntry& cacheLine = pcm_cache_data[cacheIdx];
vc.SBuffer = cacheLine.Sampledata;
@@ -172,46 +130,18 @@ static __forceinline s32 GetNextDataBuffered(V_Core& thiscore, uint voiceidx)
g_counter_cache_misses++;
}
s16* memptr = GetMemPtr(vc.NextA & 0xFFFF8);
XA_decode_block(vc.SBuffer, memptr, vc.Prev1, vc.Prev2);
}
}
return vc.SBuffer[vc.SCurrent++];
}
static __forceinline void GetNextDataDummy(V_Core& thiscore, uint voiceidx)
{
V_Voice& vc(thiscore.Voices[voiceidx]);
IncrementNextA(thiscore, voiceidx);
if ((vc.NextA & 7) == 0) // vc.SCurrent == 24 equivalent
// Get the sample index for NextA, we have to subtract 1 to ignore the loop header
int sampleIdx = ((vc.NextA % pcm_WordsPerBlock) - 1) * 4;
for (int i = 0; i < 4; i++)
{
if (vc.LoopFlags & XAFLAG_LOOP_END)
{
thiscore.Regs.ENDX |= (1 << voiceidx);
vc.NextA = vc.LoopStartA | 1;
}
else
vc.NextA++; // no, don't IncrementNextA here. We haven't read the header yet.
vc.DecodeFifo[(vc.DecPosWrite + i) % 32] = vc.SBuffer[sampleIdx + i];
}
if (vc.SCurrent == 28)
{
for (int i = 0; i < 2; i++)
if (Cores[i].IRQEnable && Cores[i].IRQA == (vc.NextA & 0xFFFF8))
SetIrqCall(i);
vc.LoopFlags = *GetMemPtr(vc.NextA & 0xFFFF8) >> 8; // grab loop flags from the upper byte.
if ((vc.LoopFlags & XAFLAG_LOOP_START) && !vc.LoopMode)
vc.LoopStartA = vc.NextA & 0xFFFF8;
vc.SCurrent = 0;
}
vc.SP -= 0x1000 * (4 - (vc.SCurrent & 3));
vc.SCurrent += 4 - (vc.SCurrent & 3);
}
/////////////////////////////////////////////////////////////////////////////////////////
@@ -237,6 +167,69 @@ static __forceinline StereoOut32 ApplyVolume(const StereoOut32& data, const V_Vo
ApplyVolume(data.Right, volume.Right.Value));
}
static __forceinline void UpdateBlockHeader(V_Core& thiscore, uint voiceidx)
{
V_Voice& vc(thiscore.Voices[voiceidx]);
for (int i = 0; i < 2; i++)
if (Cores[i].IRQEnable && Cores[i].IRQA == (vc.NextA & 0xFFFF8))
SetIrqCall(i);
s16* memptr = GetMemPtr(vc.NextA & 0xFFFF8);
vc.LoopFlags = *memptr >> 8; // grab loop flags from the upper byte.
if ((vc.LoopFlags & XAFLAG_LOOP_START) && !vc.LoopMode)
{
vc.LoopStartA = vc.NextA & 0xFFFF8;
}
}
static __forceinline void DecodeSamples(uint coreidx, uint voiceidx)
{
V_Core& thiscore(Cores[coreidx]);
V_Voice& vc(thiscore.Voices[voiceidx]);
// Update the block header on every audio frame
UpdateBlockHeader(thiscore, voiceidx);
// When a voice is started at 0 pitch, NAX quickly advances to SSA + 5
// So that would mean the decode buffer holds around 12 samples
if (((int)(vc.DecPosWrite - vc.DecPosRead)) > 12) {
// Sufficient data buffered
return;
}
if (vc.ADSR.Phase > V_ADSR::PHASE_STOPPED)
{
GetNextDataBuffered(thiscore, voiceidx);
}
vc.DecPosWrite += 4;
IncrementNextA(thiscore, voiceidx);
if ((vc.NextA & 7) == 0)
{
if (vc.LoopFlags & XAFLAG_LOOP_END)
{
thiscore.Regs.ENDX |= (1 << voiceidx);
vc.NextA = vc.LoopStartA;
if (!(vc.LoopFlags & XAFLAG_LOOP))
{
vc.Stop();
if (IsDevBuild)
{
if (SPU2::MsgVoiceOff())
SPU2::ConLog("* SPU2: Voice Off by EndPoint: %d \n", voiceidx);
}
}
}
IncrementNextA(thiscore, voiceidx);
vc.SBuffer = nullptr;
}
}
static void __forceinline UpdatePitch(uint coreidx, uint voiceidx)
{
V_Voice& vc(Cores[coreidx].Voices[voiceidx]);
@@ -278,33 +271,27 @@ static __forceinline void CalculateADSR(V_Core& thiscore, uint voiceidx)
pxAssume(vc.ADSR.Value >= 0); // ADSR should never be negative...
}
__forceinline static s32 GaussianInterpolate(s32 pv4, s32 pv3, s32 pv2, s32 pv1, s32 i)
static __forceinline void ConsumeSamples(V_Core& thiscore, uint voiceidx)
{
s32 out = 0;
out = (interpTable[i][0] * pv4) >> 15;
out += (interpTable[i][1] * pv3) >> 15;
out += (interpTable[i][2] * pv2) >> 15;
out += (interpTable[i][3] * pv1) >> 15;
V_Voice& vc(thiscore.Voices[voiceidx]);
return out;
int consumed = vc.SP >> 12;
vc.SP &= 0xfff;
vc.DecPosRead += consumed;
}
static __forceinline s32 GetVoiceValues(V_Core& thiscore, uint voiceidx)
{
V_Voice& vc(thiscore.Voices[voiceidx]);
while (vc.SP >= 0)
{
vc.PV4 = vc.PV3;
vc.PV3 = vc.PV2;
vc.PV2 = vc.PV1;
vc.PV1 = GetNextDataBuffered(thiscore, voiceidx);
vc.SP -= 0x1000;
}
int phase = (vc.SP & 0x0ff0) >> 4;
s32 out = 0;
out += (interpTable[phase][0] * vc.DecodeFifo[(vc.DecPosRead + 0) % 32]) >> 15;
out += (interpTable[phase][1] * vc.DecodeFifo[(vc.DecPosRead + 1) % 32]) >> 15;
out += (interpTable[phase][2] * vc.DecodeFifo[(vc.DecPosRead + 2) % 32]) >> 15;
out += (interpTable[phase][3] * vc.DecodeFifo[(vc.DecPosRead + 3) % 32]) >> 15;
const s32 mu = vc.SP + 0x1000;
return GaussianInterpolate(vc.PV4, vc.PV3, vc.PV2, vc.PV1, (mu & 0x0ff0) >> 4);
return out;
}
// This is Dr. Hell's noise algorithm as implemented in pcsxr
@@ -382,21 +369,13 @@ static __forceinline StereoOut32 MixVoice(uint coreidx, uint voiceidx)
V_Core& thiscore(Cores[coreidx]);
V_Voice& vc(thiscore.Voices[voiceidx]);
// If this assertion fails, it mans SCurrent is being corrupted somewhere, or is not initialized
// properly. Invalid values in SCurrent will cause errant IRQs and corrupted audio.
pxAssertMsg((vc.SCurrent <= 28) && (vc.SCurrent != 0), "Current sample should always range from 1->28");
// Most games don't use much volume slide effects. So only call the UpdateVolume
// methods when needed by checking the flag outside the method here...
// (Note: Ys 6 : Ark of Nephistm uses these effects)
vc.Volume.Update();
// SPU2 Note: The spu2 continues to process voices for eternity, always, so we
// have to run through all the motions of updating the voice regardless of it's
// audible status. Otherwise IRQs might not trigger and emulation might fail.
UpdatePitch(coreidx, voiceidx);
DecodeSamples(coreidx, voiceidx);
StereoOut32 voiceOut(0, 0);
s32 Value = 0;
@@ -419,11 +398,14 @@ static __forceinline StereoOut32 MixVoice(uint coreidx, uint voiceidx)
voiceOut = ApplyVolume(StereoOut32(Value, Value), vc.Volume);
}
else
{
while (vc.SP >= 0)
GetNextDataDummy(thiscore, voiceidx); // Dummy is enough
}
// SPU2 Note: The spu2 continues to process voices for eternity, always, so we
// have to run through all the motions of updating the voice regardless of it's
// audible status. Otherwise IRQs might not trigger and emulation might fail.
UpdatePitch(coreidx, voiceidx);
ConsumeSamples(thiscore, voiceidx);
// Write-back of raw voice data (post ADSR applied)
if (voiceidx == 1)
@@ -533,7 +515,8 @@ StereoOut32 V_Core::Mix(const VoiceMixSet& inVoices, const StereoOut32& Input, c
return TD + ApplyVolume(RV, FxVol);
}
static StereoOut32 DCFilter(StereoOut32 input) {
static StereoOut32 DCFilter(StereoOut32 input)
{
// A simple DC blocking high-pass filter
// Implementation from http://peabody.sapp.org/class/dmp2/lab/dcblock/
// The magic number 0x7f5c is ceil(INT16_MAX * 0.995)
@@ -634,9 +617,9 @@ __forceinline void spu2Mix()
if (SPU2::MsgCache())
{
SPU2::ConLog(" * SPU2 > CacheStats > Hits: %d Misses: %d Ignores: %d\n",
g_counter_cache_hits,
g_counter_cache_misses,
g_counter_cache_ignores);
g_counter_cache_hits,
g_counter_cache_misses,
g_counter_cache_ignores);
}
g_counter_cache_hits =

View File

@@ -256,29 +256,16 @@ struct V_Voice
// Sample pointer (19:12 bit fixed point)
s32 SP;
// Sample pointer for Cubic Interpolation
// Cubic interpolation mixes a sample behind Linear, so that it
// can have sample data to either side of the end points from which
// to extrapolate. This SP represents that late sample position.
s32 SPc;
// Previous sample values - used for interpolation
// Inverted order of these members to match the access order in the
// code (might improve cache hits).
s32 PV4;
s32 PV3;
s32 PV2;
s32 PV1;
// Last outputted audio value, used for voice modulation.
s32 OutX;
s32 NextCrest; // temp value for Crest calculation
// SBuffer now points directly to an ADPCM cache entry.
s16* SBuffer;
// sample position within the current decoded packet.
s32 SCurrent;
// Each voice has a buffer of decoded samples
s32 DecodeFifo[32];
u32 DecPosWrite;
u32 DecPosRead;
// it takes a few ticks for voices to start on the real SPU2?
void Start();

View File

@@ -181,7 +181,6 @@ void V_Core::Init(int index)
VoiceGates[v].WetR = -1;
Voices[v].Volume = V_VolumeSlideLR(0, 0); // V_VolumeSlideLR::Max;
Voices[v].SCurrent = 28;
Voices[v].ADSR.Counter = 0;
Voices[v].ADSR.Value = 0;
@@ -190,6 +189,10 @@ void V_Core::Init(int index)
Voices[v].NextA = 0x2801;
Voices[v].StartA = 0x2800;
Voices[v].LoopStartA = 0x2800;
memset(Voices[v].DecodeFifo, 0, sizeof(Voices[v].DecodeFifo));
Voices[v].DecPosRead = 0;
Voices[v].DecPosWrite = 0;
}
DMAICounter = 0;
@@ -212,23 +215,18 @@ void V_Voice::Start()
}
ADSR.Attack();
SCurrent = 28;
LoopMode = 0;
// When SP >= 0 the next sample will be grabbed, we don't want this to happen
// instantly because in the case of pitch being 0 we want to delay getting
// the next block header. This is a hack to work around the fact that unlike
// the HW we don't update the block header on every cycle.
SP = -1;
SP = 0;
LoopFlags = 0;
NextA = StartA | 1;
Prev1 = 0;
Prev2 = 0;
PV1 = PV2 = 0;
PV3 = PV4 = 0;
NextCrest = -0x8000;
SBuffer = nullptr;
DecPosRead = 0;
DecPosWrite = 0;
}
void V_Voice::Stop()
@@ -989,12 +987,10 @@ static void RegWrite_VoiceAddr(u16 value)
// Wallace And Gromit: Curse Of The Were-Rabbit.
thisvoice.NextA = ((u32)(value & 0x0F) << 16) | (thisvoice.NextA & 0xFFF8) | 1;
thisvoice.SCurrent = 28;
break;
case 5:
thisvoice.NextA = (thisvoice.NextA & 0x0F0000) | (value & 0xFFF8) | 1;
thisvoice.SCurrent = 28;
break;
}
}
@@ -1212,7 +1208,6 @@ static void RegWrite_Core(u16 value)
for (uint v = 0; v < 24; ++v)
{
Cores[1].Voices[v].Volume = V_VolumeSlideLR(0, 0); // V_VolumeSlideLR::Max;
Cores[1].Voices[v].SCurrent = 28;
Cores[1].Voices[v].ADSR.Value = 0;
Cores[1].Voices[v].ADSR.Phase = 0;

View File

@@ -26,7 +26,7 @@ enum class FreezeAction
// [SAVEVERSION+]
// This informs the auto updater that the users savestates will be invalidated.
static const u32 g_SaveVersion = (0x9A55 << 16) | 0x0000;
static const u32 g_SaveVersion = (0x9A57 << 16) | 0x0000;
// the freezing data between submodules and core

View File

@@ -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 = 80;
static constexpr u32 SHADER_CACHE_VERSION = 81;

View File

@@ -254,6 +254,7 @@
<ClCompile Include="Host\SDLAudioStream.cpp" />
<ClCompile Include="Hotkeys.cpp" />
<ClCompile Include="ImGui\FullscreenUI.cpp" />
<ClCompile Include="ImGui\FullscreenUI_Settings.cpp" />
<ClCompile Include="ImGui\ImGuiFullscreen.cpp" />
<ClCompile Include="ImGui\ImGuiManager.cpp" />
<ClCompile Include="ImGui\ImGuiOverlays.cpp" />
@@ -700,6 +701,7 @@
<ClInclude Include="Host\AudioStream.h" />
<ClInclude Include="Host\AudioStreamTypes.h" />
<ClInclude Include="ImGui\FullscreenUI.h" />
<ClInclude Include="ImGui\FullscreenUI_Internal.h" />
<ClInclude Include="ImGui\ImGuiAnimated.h" />
<ClInclude Include="ImGui\ImGuiFullscreen.h" />
<ClInclude Include="ImGui\ImGuiManager.h" />

View File

@@ -1352,6 +1352,9 @@
<ClCompile Include="ImGui\FullscreenUI.cpp">
<Filter>Misc\ImGui</Filter>
</ClCompile>
<ClCompile Include="ImGui\FullscreenUI_Settings.cpp">
<Filter>Misc\ImGui</Filter>
</ClCompile>
<ClCompile Include="ImGui\ImGuiFullscreen.cpp">
<Filter>Misc\ImGui</Filter>
</ClCompile>
@@ -2304,6 +2307,9 @@
<ClInclude Include="ImGui\FullscreenUI.h">
<Filter>Misc\ImGui</Filter>
</ClInclude>
<ClInclude Include="ImGui\FullscreenUI_Internal.h">
<Filter>Misc\ImGui</Filter>
</ClInclude>
<ClInclude Include="ImGui\ImGuiFullscreen.h">
<Filter>Misc\ImGui</Filter>
</ClInclude>

View File

@@ -5,90 +5,110 @@ import os
START_IDENT = "// TRANSLATION-STRING-AREA-BEGIN"
END_IDENT = "// TRANSLATION-STRING-AREA-END"
src_file = os.path.join(os.path.dirname(__file__), "..", "pcsx2", "ImGui", "FullscreenUI.cpp")
src_files = [
os.path.join(os.path.dirname(__file__), "..", "pcsx2", "ImGui", "FullscreenUI.cpp"),
os.path.join(os.path.dirname(__file__), "..", "pcsx2", "ImGui", "FullscreenUI_Settings.cpp"),
]
with open(src_file, "r") as f:
full_source = f.read()
def extract_strings_from_source(source_content):
"""Extract FSUI translation strings from source content."""
strings = []
for token in ["FSUI_STR", "FSUI_CSTR", "FSUI_FSTR", "FSUI_NSTR", "FSUI_VSTR", "FSUI_ICONSTR", "FSUI_ICONSTR_S"]:
token_len = len(token)
last_pos = 0
while True:
last_pos = source_content.find(token, last_pos)
if last_pos < 0:
break
strings = []
for token in ["FSUI_STR", "FSUI_CSTR", "FSUI_FSTR", "FSUI_NSTR", "FSUI_VSTR", "FSUI_ICONSTR", "FSUI_ICONSTR_S"]:
token_len = len(token)
last_pos = 0
while True:
last_pos = full_source.find(token, last_pos)
if last_pos < 0:
break
if last_pos >= 8 and source_content[last_pos - 8:last_pos] == "#define ":
last_pos += len(token)
continue
if last_pos >= 8 and full_source[last_pos - 8:last_pos] == "#define ":
last_pos += len(token)
continue
if source_content[last_pos + token_len] == '(':
start_pos = last_pos + token_len + 1
end_pos = source_content.find(")", start_pos)
s = source_content[start_pos:end_pos]
if full_source[last_pos + token_len] == '(':
start_pos = last_pos + token_len + 1
end_pos = full_source.find(")", start_pos)
s = full_source[start_pos:end_pos]
# Split into string arguments, removing "
string_args = [""]
arg = 0;
cpos = s.find(',')
pos = s.find('"')
while pos >= 0 or cpos >= 0:
assert pos == 0 or s[pos - 1] != '\\'
if cpos == -1 or pos < cpos:
epos = pos
while True:
epos = s.find('"', epos + 1)
# found ')' in string, extend s to next ')'
if epos == -1:
end_pos = source_content.find(")", end_pos + 1)
s = source_content[start_pos:end_pos]
epos = pos
continue
# Split into sting arguments, removing "
string_args = [""]
arg = 0;
cpos = s.find(',')
pos = s.find('"')
while pos >= 0 or cpos >= 0:
assert pos == 0 or s[pos - 1] != '\\'
if cpos == -1 or pos < cpos:
epos = pos
while True:
epos = s.find('"', epos + 1)
# found ')' in string, extend s to next ')'
if epos == -1:
end_pos = full_source.find(")", end_pos + 1)
s = full_source[start_pos:end_pos]
epos = pos
continue
if s[epos - 1] == '\\':
continue
else:
break
if s[epos - 1] == '\\':
continue
else:
break
assert epos > pos
string_args[arg] += s[pos+1:epos]
cpos = s.find(',', epos + 1)
pos = s.find('"', epos + 1)
else:
arg += 1
string_args.append("")
cpos = s.find(',', cpos + 1)
assert epos > pos
string_args[arg] += s[pos+1:epos]
cpos = s.find(',', epos + 1)
pos = s.find('"', epos + 1)
print(string_args)
# FSUI_ICONSTR and FSUI_ICONSTR_S need to translate the only the second argument
# other defines take only a single argument
if len(string_args) >= 2:
new_s = string_args[1]
else:
arg += 1
string_args.append("")
cpos = s.find(',', cpos + 1)
new_s = string_args[0]
print(string_args)
assert len(new_s) > 0
# FSUI_ICONSTR and FSUI_ICONSTR_S need to translate the only the second argument
# other defines take only a single argument
if len(string_args) >= 2:
new_s = string_args[1]
else:
new_s = string_args[0]
if new_s not in strings:
strings.append(new_s)
last_pos += len(token)
return strings
assert len(new_s) > 0
def process_file(src_file):
"""Process a single source file extract strings and update its translation area."""
print(f"\nProcessing: {src_file}")
with open(src_file, "r") as f:
source = f.read()
#assert (end_pos - start_pos) < 300
#if (end_pos - start_pos) >= 300:
# print("WARNING: Long string")
# print(new_s)
if new_s not in strings:
strings.append(new_s)
last_pos += len(token)
start = source.find(START_IDENT)
end = source.find(END_IDENT)
if start < 0 or end <= start:
print(f" Warning: No translation string area found in {src_file}")
return 0
source_without_area = source[:start] + source[end + len(END_IDENT):]
strings = extract_strings_from_source(source_without_area)
print(f" Found {len(strings)} unique strings.")
new_area = ""
for string in strings:
new_area += f"TRANSLATE_NOOP(\"FullscreenUI\", \"{string}\");\n"
new_source = source[:start + len(START_IDENT) + 1] + new_area + source[end:]
with open(src_file, "w") as f:
f.write(new_source)
return len(strings)
print(f"Found {len(strings)} unique strings.")
total_strings = 0
for src_file in src_files:
total_strings += process_file(src_file)
start = full_source.find(START_IDENT)
end = full_source.find(END_IDENT)
assert start >= 0 and end > start
new_area = ""
for string in list(strings):
new_area += f"TRANSLATE_NOOP(\"FullscreenUI\", \"{string}\");\n"
full_source = full_source[:start+len(START_IDENT)+1] + new_area + full_source[end:]
with open(src_file, "w") as f:
f.write(full_source)
print(f"\nTotal: {total_strings} unique strings across all files.")